import re
from .lexer import Lexer, TokenType, Token
from typing import Any, List, Dict, Tuple

# Sentinel value for misspelled literal checks
_SENTINEL = object()

class JSONParseError(Exception):
    """
    Exception raised for JSON parsing errors
    """
    def __init__(
        self, 
        message: str, 
        line: int = None, 
        column: int = None,
    ):
        self.line = line
        self.column = column
        
        location = f" at line {line}, column {column}" if line and column else ""
        self.message = f"JSON Parse Error{location}: {message}"
        super().__init__(self.message)

class JSONParser:
    """
    Parser for JSON text with optional auto-fixing capabilities
    """
    def __init__(
        self, 
        text: str, 
        auto_fix: bool = False,
    ):
        # Preprocess the text to handle common whitespace issues
        self.text = self._preprocess_text(text)
        self.auto_fix = auto_fix
        self.lexer = Lexer(self.text, auto_fix_mode=auto_fix)
        self.tokens, self.lexer_messages = self.lexer.tokenize()
        self.current = 0
        self.fixes_applied = []
        self.fix_limit = 20  # Maximum number of fixes before considering "badly deformed"
        
        # Common variants of JSON literals for additional auto-fix detection
        self.TRUE_VARIANTS = ('t', 'tru', 'tr', 'ture', 'true', 'truee', 'truw', 'treu', 'yes', 'on')
        self.FALSE_VARIANTS = ('f', 'fal', 'fals', 'false', 'fales', 'falsy', 'flase', 'no', 'off', '#f')
        self.NULL_VARIANTS = ('n', 'nu', 'nul', 'null', 'nill', 'nall', 'none', '', 'nil')
        
        # Apply fixes from lexer if auto_fix is enabled
        if self.auto_fix:
            for msg in self.lexer_messages:
                self._add_fix(msg)
            
            # Initialize with special notation handling
            self._refine_tokens_for_parser_autofix()

    def _clean_string_value(self, value: str) -> str:
        """
        Clean up string values from malformed JSON, including triple-quoted strings.
        """
        if not isinstance(value, str):
            return value
        
        # Handle Python-style triple quotes
        if (value.startswith('"""') and value.endswith('"""')) or \
           (value.startswith("'''") and value.endswith("'''")):
            # Strip the triple quotes from both ends
            if self.auto_fix:
                quote_type = value[:3]
                clean_value = value[3:-3]
                self._add_fix(f"Converted Python-style triple-quoted string to standard JSON string")
                return clean_value
        
        # Check if there are any escaped newlines or structural characters
        if '\\n' not in value and '\\r' not in value and '\\t' not in value and '}' not in value and ']' not in value and ',' not in value:
            return value
        
        # Step 1: Remove any trailing commas followed by whitespace or escape sequences
        # This handles cases like "value2," and "value2,\n"
        cleaned = re.sub(r',\s*\\n\s*$', '', value)  # Remove comma + newline at end
        cleaned = re.sub(r',\s*$', '', cleaned)      # Remove trailing comma at end
        
        # Step 2: Remove any remaining trailing escaped control characters
        cleaned = re.sub(r'\\n\s*$', '', cleaned)
        cleaned = re.sub(r'\\r\s*$', '', cleaned)
        cleaned = re.sub(r'\\t\s*$', '', cleaned)
        
        # Step 3: Handle cases where string contains structural characters
        if '}' in cleaned or ']' in cleaned:
            # Check for potentially unclosed string that captured JSON structure
            
            # First check for key2/value2 pattern (common in test cases)
            key_pattern_match = re.search(r'["\']([^"\']*?),\s*["\']([\w\d]+)["\']:\s*["\']([\w\d\s]+)["\']', cleaned)
            if key_pattern_match:
                # This is likely a case where we need to terminate at the comma
                comma_pos = cleaned.find(key_pattern_match.group(0)) + key_pattern_match.group(1).rstrip().rfind(',')
                if comma_pos >= 0:
                    return cleaned[:comma_pos]
            
            # Look for closing braces and brackets that should not be part of the string
            brace_pos = cleaned.rfind('}')
            bracket_pos = cleaned.rfind(']')
            
            if brace_pos >= 0 or bracket_pos >= 0:
                # Take the last structural character position
                pos = max(brace_pos, bracket_pos)
                
                # Check if this structural character is likely meant to be external
                if pos > 0:
                    # Look at what comes before - if it looks complete, trim here
                    before_char = cleaned[:pos].strip()
                    if before_char and not before_char.endswith('\\'):
                        return before_char
        
        # Step 4: Clean escaping artifacts that might appear at string boundaries
        # Handle the escaped newline before structural characters
        cleaned = re.sub(r'\\n\s*([,\}\]])', r'\1', cleaned)
        
        return cleaned
    
    def _preprocess_text(
        self, 
        text: str,
    ) -> str:
        """
        Preprocess the JSON text to handle common whitespace issues
        """
        if not text:
            return text
        
        # Strip leading and trailing whitespace, including newlines
        text = text.strip()
        
        # Handle trailing commas outside of arrays and objects
        if text.endswith(','):
            text = text[:-1]
            
        return text
    
    def _refine_tokens_for_parser_autofix(self):
        """
        Further process tokens for additional auto-fix handling
        """
        if not self.auto_fix:
            return
            
        # Look for special notation patterns like hex (0xFF) and binary (0b1010)
        new_tokens = []
        i = 0
        while i < len(self.tokens):
            token = self.tokens[i]
            
            # Handle consecutive signs like ++1 or --1
            if token.type == TokenType.IDENTIFIER and i < len(self.tokens) - 1:
                next_token = self.tokens[i + 1]
                if token.value == "+" and next_token.type == TokenType.NUMBER:
                    # Handle ++number case
                    if next_token.value >= 0:
                        new_tokens.append(next_token)
                        self._add_fix(f"Removed redundant + sign before number at line {token.line}, column {token.column}")
                        i += 2
                        continue
                elif token.value == "-" and next_token.type == TokenType.NUMBER:
                    # Handle --number case (double negative becomes positive)
                    if next_token.value < 0:
                        new_tokens.append(Token(
                            TokenType.NUMBER,
                            abs(next_token.value),
                            token.line,
                            token.column
                        ))
                        self._add_fix(f"Converted double negative to positive: --{abs(next_token.value)} → {abs(next_token.value)} at line {token.line}, column {token.column}")
                        i += 2
                        continue
            
            # Special case for trailing commas after objects/arrays at the end
            if token.type == TokenType.COMMA and i > 0 and i == len(self.tokens) - 2:  # Last token before EOF
                prev_token = self.tokens[i - 1]
                if prev_token.type in (TokenType.RIGHT_BRACE, TokenType.RIGHT_BRACKET):
                    # This is a trailing comma after a complete object/array, ignore it
                    self._add_fix(f"Removed trailing comma at the end of JSON document at line {token.line}, column {token.column}")
                    i += 1  # Skip the comma
                    continue
            
            if token.type == TokenType.IDENTIFIER and isinstance(token.value, str):
                val_str = token.value
                processed = False
                
                # Check for hexadecimal notation
                if val_str.lower().startswith("0x") and len(val_str) > 2:
                    try:
                        num_val = int(val_str, 16)
                        new_tokens.append(Token(TokenType.NUMBER, num_val, token.line, token.column))
                        self._add_fix(f"Converted hex '{val_str}' to number {num_val} at line {token.line}, column {token.column}")
                        processed = True
                    except ValueError:
                        pass
                
                # Check for binary notation
                elif val_str.lower().startswith("0b") and len(val_str) > 2:
                    try:
                        num_val = int(val_str, 2)
                        new_tokens.append(Token(TokenType.NUMBER, num_val, token.line, token.column))
                        self._add_fix(f"Converted binary '{val_str}' to number {num_val} at line {token.line}, column {token.column}")
                        processed = True
                    except ValueError:
                        pass
                
                if not processed:
                    new_tokens.append(token)
            else:
                new_tokens.append(token)
            i += 1
        
        self.tokens = new_tokens
    
    def _is_severely_deformed_json(self) -> bool:
        """
        Check if the JSON is severely deformed (too many fixes applied)
        """
        fix_count = len(self.fixes_applied)
        return fix_count > self.fix_limit
    
    def _will_exceed_fix_limit(self) -> bool:
        """Check if applying another fix will exceed the limit"""
        return len(self.fixes_applied) >= self.fix_limit
    
    def _add_fix(
        self, 
        message: str,
    ) -> bool:
        """
        Add a fix message if under the limit, return True if successful
        """
        if self._will_exceed_fix_limit():
            # We've hit the limit, don't add more fixes
            if not self.fixes_applied or "badly deformed" not in self.fixes_applied[-1]:
                self.fixes_applied.append("Warning: JSON structure was badly deformed, result may not reflect original intent")
            return False
        
        self.fixes_applied.append(message)
        return True
    
    def _check_misspelled_literal(
        self, 
        token_value: Any,
    ):
        """
        Check if a token might be a misspelled literal
        """
        if not isinstance(token_value, str):
            return _SENTINEL
            
        val_lower = token_value.lower()
        
        if val_lower in self.TRUE_VARIANTS:
            return True
        elif val_lower in self.FALSE_VARIANTS:
            return False
        elif val_lower in self.NULL_VARIANTS:
            return None
            
        return _SENTINEL
    
    def _find_json_start(self) -> bool:
        """
        Find the start of valid JSON structure and skip any content before it
        """
        json_start = None
        
        for i, token in enumerate(self.tokens):
            if token.type in (TokenType.LEFT_BRACE, TokenType.LEFT_BRACKET):
                json_start = i
                break
        
        if json_start is not None and json_start > 0:
            # Skip everything before the JSON start
            start_token = self.tokens[0]
            skipped_tokens = self.tokens[:json_start]
            skipped_text = " ".join([str(t.value) for t in skipped_tokens if t.value is not None])
            self.current = json_start
            self._add_fix(f"Skipped text before valid JSON structure: '{skipped_text}' at line {start_token.line}, column {start_token.column}")
            return True
        
        return False
    
    def _check_for_multiple_json_objects(self) -> bool:
        """
        Check if the input contains multiple JSON objects that should be wrapped in an array
        """
        objects_count = 0
        brace_level = 0
        bracket_level = 0
        potential_objects = []
        start_pos = None
        
        # Check for common pattern of multiple JSON objects: {...},{...}
        for i, token in enumerate(self.tokens):
            if token.type == TokenType.LEFT_BRACE:
                if brace_level == 0:
                    start_pos = i
                brace_level += 1
            elif token.type == TokenType.RIGHT_BRACE:
                brace_level -= 1
                if brace_level == 0 and start_pos is not None:
                    potential_objects.append((start_pos, i))
                    start_pos = None
            elif token.type == TokenType.LEFT_BRACKET:
                bracket_level += 1
            elif token.type == TokenType.RIGHT_BRACKET:
                bracket_level -= 1
            elif token.type == TokenType.COMMA and brace_level == 0 and bracket_level == 0 and len(potential_objects) > 0:
                # Found comma outside any braces/brackets, after at least one object
                objects_count += 1
        
        # If we found multiple objects, or if there's a standalone object after a comma
        return len(potential_objects) > 1 or objects_count > 0
    
    def _clean_tokens(self):
        """
        Clean up the token stream to handle common issues like trailing commas
        """
        if not self.auto_fix:
            return
        
        # Remove trailing comma if present after a complete object/array
        if len(self.tokens) >= 3:  # Need at least closing brace, comma, and EOF
            last_idx = len(self.tokens) - 2  # The token before EOF
            if self.tokens[last_idx].type == TokenType.COMMA:
                prev_idx = last_idx - 1
                if prev_idx >= 0 and self.tokens[prev_idx].type in (TokenType.RIGHT_BRACE, TokenType.RIGHT_BRACKET):
                    self._add_fix(f"Removed trailing comma at end of JSON at line {self.tokens[last_idx].line}, column {self.tokens[last_idx].column}")
                    # Remove the comma token
                    self.tokens.pop(last_idx)
    
    def _is_in_property_context(self) -> bool:
        """
        Check if we're currently in a context where we might be parsing a property value
        """
        # Look backward for a COLON which would indicate we're in a property value
        if self.current > 0:
            for i in range(self.current-1, max(0, self.current-5), -1):
                if self.tokens[i].type == TokenType.COLON:
                    return True
        return False
    
    def _is_current_token_a_key_candidate(self) -> bool:
        """
        Checks if the current token and the next form a 'KEY : ' pattern.
        """
        if self.current + 1 >= len(self.tokens): # Need at least two tokens (current and next)
            return False

        current_token = self.tokens[self.current]
        next_token = self.tokens[self.current + 1]

        # A key candidate is a STRING or IDENTIFIER followed by a COLON
        if (current_token.type == TokenType.STRING or current_token.type == TokenType.IDENTIFIER) and \
           next_token.type == TokenType.COLON:
            return True
        
        # Also check for a slightly looser pattern in auto-fix mode
        if self.auto_fix and current_token.type == TokenType.STRING:
            # Look ahead a few tokens to find a colon
            look_ahead = 2
            while self.current + look_ahead < len(self.tokens) and look_ahead < 5:  # Reasonable limit
                token = self.tokens[self.current + look_ahead]
                if token.type == TokenType.COLON:
                    return True
                look_ahead += 1
        
        return False
    
    def parse(self) -> Any:
        """
        Parse JSON text and return the resulting data structure
        """
        # Handle preprocessing of tokens
        self._clean_tokens()
        
        # Check for empty input
        if not self.tokens or (len(self.tokens) == 1 and self.tokens[0].type == TokenType.EOF):
            if self.auto_fix:
                if not self._add_fix("Added default value (null) for empty input"):
                    return None
                return None
            raise JSONParseError("No tokens to parse", 1, 1)
        
        # In auto_fix mode, handle extra text before JSON and multiple top-level items
        if self.auto_fix:
            # Skip any text before valid JSON
            self._find_json_start()
            
            # Check for multiple JSON objects pattern at top level
            has_multiple_objects = self._check_for_multiple_json_objects()
            
            # Try to parse multiple top-level items
            items = []
            
            try:
                # Parse the first item
                first_item = self.parse_value()
                items.append(first_item)
                
                # Check for more items after the first one
                while not self.is_at_end() and self.peek().type != TokenType.EOF:
                    # Skip any separators or whitespace
                    while (not self.is_at_end() and 
                          self.peek().type not in (TokenType.LEFT_BRACE, TokenType.LEFT_BRACKET, TokenType.EOF)):
                        # If we find a comma between objects, that's a good sign of multiple objects
                        if self.peek().type == TokenType.COMMA:
                            self._add_fix(f"Found comma separator between multiple JSON objects at line {self.peek().line}, column {self.peek().column}")
                        self.advance()
                    
                    if self.is_at_end() or self.peek().type == TokenType.EOF:
                        break
                        
                    # Found another potential JSON item
                    item_token = self.peek()
                    
                    # Only proceed if we have a valid start of a JSON value 
                    if item_token.type in (TokenType.LEFT_BRACE, TokenType.LEFT_BRACKET):
                        try:
                            next_item = self.parse_value()
                            items.append(next_item)
                            self._add_fix(f"Found additional JSON object at line {item_token.line}, column {item_token.column}")
                        except Exception as e:
                            error_msg = str(e)
                            # If this is a JSONParseError, get the message attribute
                            if hasattr(e, 'message'):
                                error_msg = e.message
                            self._add_fix(f"Failed to parse additional JSON object: {error_msg}")
                            break
                    else:
                        # Not a valid JSON start, break
                        break
                
                # Return based on what we found
                if len(items) == 0:
                    return None
                elif len(items) == 1:
                    return items[0]
                else:
                    if has_multiple_objects:
                        self._add_fix(f"Wrapped {len(items)} separate JSON objects in an array")
                    else:
                        self._add_fix(f"Combined {len(items)} separate JSON objects into an array")
                    return items
                
            except JSONParseError as e:
                # In auto_fix mode, try to recover; otherwise re-raise
                if not self._add_fix(f"Failed to parse: {e.message}"):
                    return None
                return None
                
        # Standard parsing for non-auto_fix mode or failed multi-item parsing
        try:
            result = self.parse_value()
            
            # Check for unconsumed tokens
            if not self.is_at_end() and self.peek().type != TokenType.EOF:
                token = self.peek()
                if self.auto_fix:
                    # Special handling for trailing commas at the end of the document
                    if token.type == TokenType.COMMA and self.current + 1 < len(self.tokens) and self.tokens[self.current + 1].type == TokenType.EOF:
                        self._add_fix(f"Removed trailing comma at the end of JSON document at line {token.line}, column {token.column}")
                        self.advance() # Skip the comma
                        return result
                    
                    # Make sure we record the fix for extra content
                    extra_tokens = []
                    current_pos = self.current
                    
                    while current_pos < len(self.tokens) and self.tokens[current_pos].type != TokenType.EOF:
                        token_val = self.tokens[current_pos].value or self.tokens[current_pos].type.name
                        extra_tokens.append(str(token_val))
                        current_pos += 1
                    
                    extra_content = " ".join(extra_tokens)
                    if len(extra_content) > 30:  # Truncate if too long
                        extra_content = extra_content[:27] + "..."
                        
                    fix_msg = f"Ignored extra content after JSON document: '{extra_content}' at line {token.line}, column {token.column}"
                    if not self._add_fix(fix_msg):
                        return None  # Stop processing if we hit the fix limit
                    
                    # Skip all remaining tokens
                    while not self.is_at_end() and self.peek().type != TokenType.EOF:
                        self.advance()
                else:
                    raise JSONParseError(f"Unexpected content after JSON document", token.line, token.column)
            
            return result
        
        except JSONParseError as e:
            # In auto_fix mode, try to recover; otherwise re-raise
            if self.auto_fix:
                if not self._add_fix(f"Failed to parse: {e.message}"):
                    return None
                return None
            raise
        except Exception as e:
            # Handle unexpected errors
            token_info = "unknown location"
            try:
                if not self.is_at_end():
                    token = self.peek()
                    token_info = f"near line {token.line}, column {token.column}"
                elif self.current > 0:
                    token = self.previous()
                    token_info = f"after line {token.line}, column {token.column}"
            except:
                pass
                
            if self.auto_fix:
                if not self._add_fix(f"Unhandled parser error ({str(e)}) at {token_info}"):
                    return None
                return None
            
            if "token_info" in locals() and "token" in locals():
                raise JSONParseError(f"Unhandled parser error: {str(e)}", token.line, token.column)
            raise JSONParseError(f"Unhandled parser error: {str(e)}")
    
    def parse_value(self) -> Any:
        """
        Parse a JSON value based on the current token type
        """
        if self.is_at_end():
            prev_line, prev_col = (self.previous().line, self.previous().column) if self.current > 0 and self.tokens else (1,1)
            if self.auto_fix:
                if not self._add_fix(f"Added missing value (null) at end of input (line {prev_line}, column {prev_col})"):
                    return None
                return None
            raise JSONParseError("Unexpected end of input", prev_line, prev_col)
            
        token = self.peek()
        
        if token.type == TokenType.LEFT_BRACE:
            return self.parse_object()
        elif token.type == TokenType.LEFT_BRACKET:
            return self.parse_array()
        elif token.type == TokenType.STRING:
            initial_string_token = self.advance() # current is now at the token AFTER initial_string_token
            
            if self.auto_fix:
                # Clean the string value first to handle unterminated strings with structural characters
                initial_string_value = self._clean_string_value(initial_string_token.value)
                string_value_parts = [initial_string_value]
                first_token_line = initial_string_token.line
                first_token_column = initial_string_token.column
                
                # Check if the initial fragment itself had quotes, which makes aggressive merging more likely
                fragment_had_quotes = '"' in initial_string_value or "'" in initial_string_value
                if fragment_had_quotes:
                    if not self._add_fix(f"Initial string fragment '{initial_string_value}' (line {first_token_line}, col {first_token_column}) contained quotes, attempting robust merge."):
                        return initial_string_value # Stop if fix limit hit
                
                # Determine if we are parsing a value within an object (after a colon)
                is_property_value_context = self._is_in_property_context()

                # Check if the string value contains a closing brace or bracket which should be external
                if ('}' in initial_string_value or ']' in initial_string_value):
                    # This may be an unterminated string that incorrectly included closing braces/brackets
                    last_closing_brace = initial_string_value.rfind('}')
                    last_closing_bracket = initial_string_value.rfind(']')
                    
                    # Find the position of the last structural character that should be external
                    last_pos = max(last_closing_brace, last_closing_bracket)
                    
                    if last_pos >= 0:
                        # Split the string at this position
                        actual_string = initial_string_value[:last_pos]
                        if not self._add_fix(f"Fixed unterminated string by removing trailing closing characters at line {first_token_line}, column {first_token_column}"):
                            return initial_string_value
                        return actual_string

                while not self.is_at_end():
                    if self._will_exceed_fix_limit() and len(string_value_parts) > 1:
                        if not self.fixes_applied or "badly deformed" not in self.fixes_applied[-1]:
                            self.fixes_applied.append("Warning: JSON structure was badly deformed during string concatenation, result may not reflect original intent")
                        merged_result = " ".join(string_value_parts) if len(string_value_parts) > 1 else string_value_parts[0]
                        return self._clean_string_value(merged_result)

                    # If the current token sequence looks like a new "key": pair, stop merging
                    if self._is_current_token_a_key_candidate():
                        break 

                    peek_token = self.peek()
                    
                    # Scenario 1: Current token is a COMMA. Check if it's part of the string.
                    if peek_token.type == TokenType.COMMA:
                        if self.current + 1 < len(self.tokens):
                            token_after_comma = self.tokens[self.current + 1]
                            
                            # Check if "token_after_comma : token_after_that" is a new key pattern
                            is_next_sequence_a_key = False
                            if self.current + 2 < len(self.tokens):
                                if (token_after_comma.type == TokenType.STRING or token_after_comma.type == TokenType.IDENTIFIER) and \
                                self.tokens[self.current + 2].type == TokenType.COLON:
                                    is_next_sequence_a_key = True
                            
                            if not is_next_sequence_a_key: # Only merge if token_after_comma isn't starting a new key
                                is_continuing_string_literal = token_after_comma.type == TokenType.STRING

                                # Merge comma and next token if in property context or initial fragment had quotes,
                                # and the next token looks like a continuation.
                                if (is_property_value_context or fragment_had_quotes) and \
                                is_continuing_string_literal:
                                    
                                    self.advance() # Consume comma
                                    string_value_parts.append(str(peek_token.value)) # Add comma's value
                                    if not self._add_fix(f"Auto-fix: Merged comma into string at line {peek_token.line}, column {peek_token.column}"):
                                        break
                                    
                                    self.advance() # Consume token_after_comma
                                    string_value_parts.append(str(token_after_comma.value))
                                    if not self._add_fix(f"Auto-fix: Concatenated '{token_after_comma.value}' (type {token_after_comma.type.name}) after comma into string at line {token_after_comma.line}, column {token_after_comma.column}"):
                                        break
                                    continue # Restart loop to check next token
                        # If comma can't be merged, it's structural
                        break 
                
                    # Scenario 2: Current token is a STRING that's part of the string
                    elif peek_token.type == TokenType.STRING:
                        # Merge if it's a string in a suitable context
                        if peek_token.type == TokenType.STRING and (is_property_value_context or fragment_had_quotes):
                            self.advance() # Consume peek_token
                            string_value_parts.append(str(peek_token.value))
                            if not self._add_fix(f"Auto-fix: Concatenated '{peek_token.value}' (type {peek_token.type.name}) into string at line {peek_token.line}, column {peek_token.column}"):
                                break
                            continue # Restart loop
                    
                    # If token is not a comma or string, stop merging
                    break
                
                result = " ".join(string_value_parts) if len(string_value_parts) > 1 else string_value_parts[0]
                # Clean one final time before returning
                return self._clean_string_value(result)
            else: # Not auto_fix mode
                return initial_string_token.value
        elif token.type == TokenType.NUMBER:
            self.advance()
            return token.value
        elif token.type == TokenType.TRUE:
            self.advance()
            return True
        elif token.type == TokenType.FALSE:
            self.advance()
            return False
        elif token.type == TokenType.NULL:
            self.advance()
            return None
        elif self.auto_fix and token.type == TokenType.IDENTIFIER:
            self.advance()
            literal_value = self._check_misspelled_literal(token.value)
            if literal_value is not _SENTINEL:
                value_desc = "null" if literal_value is None else f"boolean {str(literal_value).lower()}"
                if not self._add_fix(f"Fixed misspelled keyword to {value_desc} at line {token.line}, column {token.column}"):
                    return None
                return literal_value
            
            value_lower = str(token.value).lower()
            if value_lower == 'nan':
                if not self._add_fix(f"Converted JavaScript NaN to null at line {token.line}, column {token.column}"):
                    return None
                return None
            elif value_lower in ('infinity', '-infinity'):
                if not self._add_fix(f"Converted JavaScript {token.value} to string at line {token.line}, column {token.column}"):
                    return None
                return str(token.value)
            elif value_lower == 'undefined':
                if not self._add_fix(f"Converted JavaScript undefined to null at line {token.line}, column {token.column}"):
                    return None
                return None
            
            # Additional handling for identifiers that might be part of embedded quotes
            if '"' in str(token.value) or "'" in str(token.value):
                if not self._add_fix(f"Fixed identifier with embedded quotes: '{token.value}' at line {token.line}, column {token.column}"):
                    return None
            
            if not self._add_fix(f"Treated unquoted identifier '{token.value}' as string at line {token.line}, column {token.column}"):
                return None
            return token.value
        elif self.auto_fix and token.type in (TokenType.RIGHT_BRACE, TokenType.RIGHT_BRACKET, TokenType.COMMA):
            if not self._add_fix(f"Added null value for missing value at line {token.line}, column {token.column}"):
                return None
            return None
        else:
            if self.auto_fix:
                self.advance()
                if not self._add_fix(f"Skipped invalid token '{token.value or token.type.name}' at line {token.line}, column {token.column}"):
                    return None
                return None # Return None for skipped invalid token
            raise JSONParseError(
                f"Unexpected token: {token.value or token.type.name}",
                token.line, token.column
            )

    def parse_object(self) -> Dict[str, Any]:
        """
        Parse a JSON object
        """
        obj = {}
        start_token = self.peek()
        
        self.consume(TokenType.LEFT_BRACE, "Expected '{'")
        if self._is_severely_deformed_json() and self.peek().type != TokenType.RIGHT_BRACE:
            return None
        
        if self.check(TokenType.RIGHT_BRACE):
            self.advance()
            return obj
        
        while True:
            if self.is_at_end():
                if self.auto_fix:
                    if not self._add_fix(f"Added missing closing brace for object at line {start_token.line}, column {start_token.column}"):
                        return None
                    return obj
                raise JSONParseError("Object not properly closed with '}'", start_token.line, start_token.column)
                
            if self.check(TokenType.RIGHT_BRACE):
                self.advance()
                break
            
            # Check for property key (string or identifier)
            key_token = self.peek()
            key = None
            
            # Handle case where we're expecting a key but have a string not followed by a colon
            if self.auto_fix and key_token.type == TokenType.STRING:
                # Look ahead to check if this string is followed by a colon (proper key)
                has_colon_after = False
                look_ahead = 1
                
                while self.current + look_ahead < len(self.tokens):
                    next_token = self.tokens[self.current + look_ahead]
                    if not next_token.type in (TokenType.IDENTIFIER, TokenType.STRING) and not (isinstance(next_token.value, str) and next_token.value.isspace()):
                        if next_token.type == TokenType.COLON:
                            has_colon_after = True
                        break
                    look_ahead += 1
                
                # If we have a string not followed by colon, but followed by another string and then a colon
                # e.g., "key1": "value1" "key2": "value2" - missing comma detection
                if not has_colon_after and look_ahead < len(self.tokens) - 1:
                    next_look = self.tokens[self.current + look_ahead]
                    if next_look.type == TokenType.STRING:
                        further_look = self.current + look_ahead + 1
                        while further_look < len(self.tokens):
                            further_token = self.tokens[further_look]
                            if further_token.type == TokenType.COLON:
                                # Detected missing comma between properties!
                                if not self._add_fix(f"Added missing comma between object properties at line {next_look.line}, column {next_look.column}"):
                                    return None
                                break
                            elif not further_token.type in (TokenType.IDENTIFIER, TokenType.STRING) and not (isinstance(further_token.value, str) and further_token.value.isspace()):
                                break  # Not a property pattern
                            further_look += 1
                                
            if key_token.type == TokenType.STRING:
                self.advance()
                key = key_token.value
                
                # Handle object keys with embedded quotes
                if self.auto_fix and ('"' in key or "'" in key):
                    if not self._add_fix(f"Fixed object key with embedded quotes: '{key}' at line {key_token.line}, column {key_token.column}"): 
                        return None
                    
                if self.auto_fix and key == "":
                    if not self._add_fix(f"Removed empty key-value pair at line {key_token.line}, column {key_token.column}"):
                        return None
                    if self.check(TokenType.COLON):
                        self.advance()
                    _ = self.parse_value()
                    if self._is_severely_deformed_json():
                        return None
                    if self.check(TokenType.COMMA):
                        self.advance()
                    continue
            elif self.auto_fix and key_token.type == TokenType.IDENTIFIER:
                self.advance()
                key = key_token.value
                
                # Handle identifiers that might contain embedded quotes
                if '"' in str(key) or "'" in str(key):
                    if not self._add_fix(f"Fixed unquoted key with embedded quotes: '{key}' at line {key_token.line}, column {key_token.column}"): 
                        return None
                
                if not self._add_fix(f"Treated unquoted identifier '{key}' as object key at line {key_token.line}, column {key_token.column}"):
                    return None
            elif self.auto_fix and key_token.type == TokenType.NUMBER:
                self.advance()
                key = str(key_token.value)
                if not self._add_fix(f"Converted numeric key {key_token.value} to string key '{key}' at line {key_token.line}, column {key_token.column}"):
                    return None
            else:
                if self.auto_fix:
                    if key_token.type == TokenType.RIGHT_BRACE:
                        self.advance()
                        return obj
                    self.advance()
                    if not self._add_fix(f"Skipped invalid key token: {key_token.value or key_token.type.name} at line {key_token.line}, column {key_token.column}"):
                        return None
                    # Try to recover by skipping to next potential comma or brace
                    while not self.is_at_end() and self.peek().type not in [TokenType.COMMA, TokenType.RIGHT_BRACE]:
                        self.advance()
                    if self.check(TokenType.COMMA):
                        self.advance()
                    continue
                raise JSONParseError(f"Expected string key, got '{key_token.value or key_token.type.name}'", key_token.line, key_token.column)

            if not self.check(TokenType.COLON):
                if self.auto_fix:
                    if self._will_exceed_fix_limit():
                        if not self.fixes_applied or "badly deformed" not in self.fixes_applied[-1]:
                            self.fixes_applied.append("Warning: JSON structure was badly deformed (missing colon), result may not reflect original intent")
                        return None
                    if not self._add_fix(f"Added missing ':' after object key '{key}'"):
                        return None
                else:
                    token = self.peek()
                    raise JSONParseError(f"Expected ':' after object key '{key}'", token.line, token.column)
            else:
                self.consume(TokenType.COLON, "Expected ':'")
            
            value = self.parse_value()

            # Clean up string values
            if self.auto_fix and isinstance(value, str):
                value = self._clean_string_value(value)

            # Special handling for unterminated strings that might contain closing braces
            if self.auto_fix and isinstance(value, str) and '}' in value and not self.check(TokenType.RIGHT_BRACE):
                # This string might have incorrectly included a closing brace
                pos = value.rfind('}')
                if pos >= 0:
                    # Fix the string by removing the closing brace and everything after it
                    fixed_value = value[:pos]
                    if not self._add_fix(f"Fixed unterminated string that included closing brace: '{value}' -> '{fixed_value}'"):
                        return None
                    value = fixed_value
                    
                    # There was a closing brace in the string - assume we're at the end of the object
                    if not self._add_fix(f"Assuming object ends after fixing unterminated string"):
                        return None
                    obj[key] = value
                    return obj

            if value is None and self._is_severely_deformed_json():
                return None
            obj[key] = value
            
            if self.is_at_end():
                if self.auto_fix:
                    if not self._add_fix(f"Added missing closing brace for object at line {start_token.line}, column {start_token.column}"):
                        return None
                    return obj
                raise JSONParseError("Object not properly closed with '}'", start_token.line, start_token.column)
                    
            if self.check(TokenType.COMMA):
                self.advance()
                if self.check(TokenType.RIGHT_BRACE):
                    if self.auto_fix:
                        if not self._add_fix(f"Removed trailing comma in object at line {self.peek().line}, column {self.peek().column}"):
                            return None
                        self.advance()
                        return obj
                    token = self.peek()
                    raise JSONParseError("Trailing comma in object", token.line, token.column)
            elif self.check(TokenType.RIGHT_BRACE):
                self.advance()
                break
            else:
                if self.auto_fix:
                    next_token = self.peek()
                    if next_token.type in (TokenType.STRING, TokenType.IDENTIFIER, TokenType.NUMBER):
                        # Missing comma between properties
                        if not self._add_fix(f"Added missing comma between object properties at line {next_token.line}, column {next_token.column}"):
                            return None
                        continue
                    else:
                        if not self._add_fix(f"Added missing closing brace for object at line {start_token.line}, column {start_token.column}"):
                            return None
                        return obj
                token = self.peek()
                raise JSONParseError(f"Expected ',' or '}}' after object value, got '{token.value or token.type.name}'", token.line, token.column)
        return obj
    
    def parse_array(self) -> List[Any]:
        """
        Parse a JSON array
        """
        array = []
        start_token = self.peek()
        
        # Consume the opening bracket
        self.consume(TokenType.LEFT_BRACKET, "Expected '['")
        
        # Empty array
        if self.check(TokenType.RIGHT_BRACKET):
            self.advance()
            return array
        
        # Parse values
        while True:
            # Check for leading or consecutive commas
            if self.check(TokenType.COMMA):
                if self.auto_fix:
                    comma_token = self.peek()
                    self.advance()  # Skip the comma
                    if len(array) == 0:
                        # Leading comma
                        if not self._add_fix(f"Removed leading comma in array at line {comma_token.line}, column {comma_token.column}"):
                            return None
                    else:
                        # Consecutive comma, already handled by the loop
                        if not self._add_fix(f"Removed extra comma in array at line {comma_token.line}, column {comma_token.column}"):
                            return None
                    continue
                else:
                    token = self.peek()
                    raise JSONParseError(
                        f"Unexpected comma in array",
                        token.line, token.column
                    )
                    
            # Check for closing bracket immediately (empty or all values already processed)
            if self.check(TokenType.RIGHT_BRACKET):
                self.advance()
                break
                
            # Check for end of input
            if self.is_at_end():
                if self.auto_fix:
                    if not self._add_fix(f"Added missing closing bracket for array at line {start_token.line}, column {start_token.column}"):
                        return None
                    return array
                raise JSONParseError(
                    "Array not properly closed with ']'", 
                    start_token.line, start_token.column
                )
                
            # Check for mismatched closing brace
            if self.auto_fix and self.check(TokenType.RIGHT_BRACE):
                if not self._add_fix(f"Fixed mismatched bracket/brace at line {self.peek().line}, column {self.peek().column}"):
                    return None
                self.advance()
                break
            
            # Parse value
            value = self.parse_value()
            
            # Clean up string values
            if self.auto_fix and isinstance(value, str):
                value = self._clean_string_value(value)
                
            # If value is None due to fix limit, stop parsing
            if value is None and self.auto_fix and self._will_exceed_fix_limit():
                return None
            
            array.append(value)
            
            # Check for comma or closing bracket
            if self.is_at_end():
                if self.auto_fix:
                    if not self._add_fix(f"Added missing closing bracket for array at line {start_token.line}, column {start_token.column}"):
                        return None
                    return array
                raise JSONParseError(
                    "Array not properly closed with ']'", 
                    start_token.line, start_token.column
                )
                    
            if self.check(TokenType.COMMA):
                self.advance()
                
                # Handle trailing comma
                if self.check(TokenType.RIGHT_BRACKET) or (self.auto_fix and self.check(TokenType.RIGHT_BRACE)):
                    if self.auto_fix:
                        fix_msg = "Removed trailing comma in array"
                        if self.check(TokenType.RIGHT_BRACE):
                            fix_msg += " and fixed mismatched bracket/brace"
                        
                        if not self._add_fix(f"{fix_msg} at line {self.peek().line}, column {self.peek().column}"):
                            return None
                        self.advance()
                        return array
                    else:
                        if self.check(TokenType.RIGHT_BRACKET):
                            token = self.peek()
                            raise JSONParseError(
                                "Trailing comma in array",
                                token.line, token.column
                            )
                elif self.is_at_end() and self.auto_fix:
                    # Handle comma at end of input
                    if not self._add_fix(f"Added missing closing bracket after trailing comma at end of input"):
                        return None
                    return array
            elif self.check(TokenType.RIGHT_BRACKET):
                self.advance()
                break
            elif self.auto_fix and self.check(TokenType.RIGHT_BRACE):
                if not self._add_fix(f"Fixed mismatched bracket/brace at line {self.peek().line}, column {self.peek().column}"):
                    return None
                self.advance()
                break
            else:
                if self.auto_fix:
                    # Check if this might be another value (missing comma)
                    next_token = self.peek()
                    if not next_token.type in (TokenType.RIGHT_BRACKET, TokenType.RIGHT_BRACE, TokenType.COMMA):
                        if not self._add_fix(f"Added missing comma between array elements at line {next_token.line}, column {next_token.column}"):
                            return None
                        continue
                    else:
                        # If we can't figure out what's happening, assume array is done
                        if not self._add_fix(f"Added missing closing bracket for array at line {start_token.line}, column {start_token.column}"):
                            return None
                        return array
                token = self.peek()
                raise JSONParseError(
                    f"Expected ',' or ']' after array element, got '{token.value or token.type.name}'",
                    token.line, token.column
                )
        
        return array
    
    def check(
        self, 
        type: TokenType,
    ) -> bool:
        """
        Check if the current token is of the given type
        """
        if self.is_at_end():
            return False
        return self.peek().type == type
    
    def consume(
        self, 
        type: TokenType, 
        message: str,
    ) -> Token:
        """
        Consume a token of the expected type or report an error
        """
        if self.check(type):
            return self.advance()
        
        if self.auto_fix:
            # Record the fix attempt
            token = self.peek()
            token_value = token.value if token.value is not None else token.type.name
            if not self._add_fix(f"Expected {type.name} but got '{token_value}' at line {token.line}, column {token.column}"):
                # Return None to indicate an error due to fix limit
                return None
            
            # Special case for mismatched brackets/braces
            if ((type == TokenType.RIGHT_BRACKET and token.type == TokenType.RIGHT_BRACE) or
                (type == TokenType.RIGHT_BRACE and token.type == TokenType.RIGHT_BRACKET)):
                if not self._add_fix(f"Fixed mismatched bracket/brace at line {token.line}, column {token.column}"):
                    return None
                self.advance()
                # Return a dummy token of the expected type
                return Token(type, None, token.line, token.column)
            
            # Return a dummy token of the expected type without consuming
            return Token(type, None, token.line, token.column)
        
        token = self.peek()
        token_value = token.value if token.value is not None else token.type.name
        raise JSONParseError(
            f"{message}, got '{token_value}'", 
            token.line, token.column
        )
    
    def advance(self) -> Token:
        """
        Advance to the next token
        """
        if not self.is_at_end():
            self.current += 1
        return self.previous()
    
    def is_at_end(self) -> bool:
        """
        Check if we've reached the end of the token stream
        """
        return self.current >= len(self.tokens) or self.peek().type == TokenType.EOF
    
    def peek(self) -> Token:
        """
        Return the current token without consuming it
        """
        if self.current >= len(self.tokens):
            # This should be prevented by is_at_end checks, but just in case
            last_token = self.tokens[-1] if self.tokens else Token(TokenType.EOF, None, 1, 1)
            return Token(TokenType.EOF, None, last_token.line, last_token.column + 1)
        return self.tokens[self.current]
    
    def previous(self) -> Token:
        """
        Return the most recently consumed token
        """
        if self.current <= 0:
            # This should not happen, but just in case
            return Token(TokenType.EOF, None, 1, 1)
        return self.tokens[self.current - 1]
    
    def get_fixes(self) -> List[str]:
        """
        Return a list of all fixes applied during parsing
        """
        return self.fixes_applied

def parse_json(
    json_text: str,
    fix_mode: bool = True,
) -> Tuple[Any, List[str]]:
    """
    Parse JSON text and return both the parsed data and a list of fixes applied.
    
    Args:
        json_text: The JSON string to parse
        
    Returns:
        Tuple containing (parsed_data, list_of_fixes)
        
    Raises:
        JSONParseError: When the JSON is too badly deformed for auto-fixing
    """
    try:
        # Attempt to parse the JSON text
        parser = JSONParser(json_text)
        result = parser.parse()
        fixes = parser.get_fixes()
        return result, fixes
    except JSONParseError:    
        if not fix_mode: raise
        else: pass
    
    # If parsing fails, try with auto-fix enabled
    parser = JSONParser(
        json_text, 
        auto_fix=True
    )
    result = parser.parse()
    fixes = parser.get_fixes()
    
    # Check if we got None due to exceeding fix limit
    if result is None and fixes:
        # Check if the badly deformed message is in the fixes
        if any("badly deformed" in fix for fix in fixes):
            # Extract the original error if possible
            original_error = None
            for fix in fixes:
                if fix.startswith("Failed to parse:"):
                    original_error = fix.replace("Failed to parse: ", "")
                    break
            
            error_msg = "JSON structure is badly deformed and exceeds auto-fix limit"
            if original_error:
                error_msg += f" - original error: {original_error}"
            
            raise JSONParseError(error_msg)
    
    return result, fixes