"""Tracklist file parsing with flexible format support."""

import re


def parse_format(format_str: str):
    """
    Convert a format string (e.g., "%ts %tn - %an") into a list of tokens.

    Each token is a dict with keys:
        'type': 'literal' or 'placeholder'
        'value': the literal text or the placeholder name (e.g., 'tn')

    Placeholders are identified by a '%' followed by alphabetic characters.
    A literal '%%' is escaped to a single '%'.

    Args:
        format_str: The user‑provided format string.

    Returns:
        List of token dicts.

    Raises:
        ValueError: If the format is invalid (e.g., unknown placeholder).
    """
    # Known placeholders.
    valid_placeholders = {'ts', 'tn', 'an', 'al', 'date', 'ext'}

    tokens = []
    i = 0
    while i < len(format_str):
        ch = format_str[i]
        if ch == '%':
            # Check for escaped '%%'.
            if i + 1 < len(format_str) and format_str[i + 1] == '%':
                tokens.append({'type': 'literal', 'value': '%'})
                i += 2
                continue
            # Must be a placeholder.
            # Match '%' followed by letters.
            match = re.match(r'%([a-zA-Z]+)', format_str[i:])
            if not match:
                raise ValueError(f"Invalid placeholder at position {i}: '{format_str[i:]}'")
            placeholder = match.group(1)
            if placeholder not in valid_placeholders:
                raise ValueError(f"Unknown placeholder '%{placeholder}'. "
                                 f"Allowed: {', '.join(valid_placeholders)}")
            tokens.append({'type': 'placeholder', 'value': placeholder})
            i += len(match.group(0))
        else:
            # Literal character.
            # Collect consecutive non‑'%' characters.
            j = i
            while j < len(format_str) and format_str[j] != '%':
                j += 1
            tokens.append({'type': 'literal', 'value': format_str[i:j]})
            i = j
    return tokens


def parse_line(line: str, tokens):
    """
    Parse a single line of the tracklist using the token list.

    Returns a dict mapping placeholder names to extracted strings.
    If a placeholder is not found, its value is None.

    Args:
        line: The raw line from the tracklist file.
        tokens: The token list from parse_format().

    Returns:
        A dict with keys for each placeholder present in the format.

    Raises:
        ValueError: If the line cannot be parsed according to the format.
    """
    # Strip leading/trailing whitespace but preserve internal spaces.
    line = line.strip()
    if not line:
        raise ValueError("Empty line")

    result = {}
    pos = 0
    # We need to match the tokens in order.
    for token in tokens:
        if token['type'] == 'literal':
            literal = token['value']
            if not line.startswith(literal, pos):
                raise ValueError(f"Expected literal '{literal}' at position {pos}, got '{line[pos:]}'")
            pos += len(literal)
        else:  # placeholder
            placeholder = token['value']
            # If this is the last token, capture the rest of the line.
            if token is tokens[-1]:
                result[placeholder] = line[pos:].strip() or None
                pos = len(line)
            else:
                # Find the next literal to use as a delimiter.
                # We need to look ahead to the next literal token.
                next_literal = None
                for next_token in tokens[tokens.index(token) + 1:]:
                    if next_token['type'] == 'literal':
                        next_literal = next_token['value']
                        break
                if next_literal is None:
                    # If no more literals, capture the rest.
                    result[placeholder] = line[pos:].strip() or None
                    pos = len(line)
                else:
                    # Find the occurrence of the next literal in the line starting from pos.
                    next_pos = line.find(next_literal, pos)
                    if next_pos == -1:
                        raise ValueError(f"Could not find literal '{next_literal}' after placeholder '{placeholder}'")
                    result[placeholder] = line[pos:next_pos].strip() or None
                    pos = next_pos
    return result


def read_tracklist(filepath: str, tokens):
    """
    Read and parse the tracklist file using the provided token list.

    Each line is parsed into a dict of fields. The timestamp field ('ts')
    is required; if missing, an error is raised.

    Args:
        filepath: Path to the tracklist file.
        tokens: Token list from parse_format().

    Returns:
        A list of dicts, one per track.

    Raises:
        ValueError: If a line cannot be parsed or the timestamp is missing.
    """
    tracks = []
    with open(filepath, 'r', encoding='utf-8') as file:
        for line_num, raw_line in enumerate(file, 1):
            line = raw_line.strip()
            if not line:
                continue
            try:
                parsed = parse_line(line, tokens)
            except ValueError as error:
                print(f"Warning: Skipping line {line_num}: {error}")
                continue

            # Ensure timestamp is present.
            if 'ts' not in parsed or parsed['ts'] is None:
                raise ValueError(f"Line {line_num}: timestamp (%ts) is missing or empty")

            tracks.append(parsed)

    return tracks
