from random import Random
from typing import Optional


def gen_field(size: int, color: int = 0) -> list[int]:
    """Generate a field of given size filled with specified color (default 0)."""
    return [color] * size


def write_block(pos: int, block: list[int], field: list[int]) -> list[int]:
    """Write a block into a field at given position."""
    result = field.copy()
    for i, color in enumerate(block):
        result[pos + i] = color
    return result


def task_move_n_pix(rng: Random, size: int, move_pix: int, solid: bool) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block is moved to the right by move_pix pixels."""
    if size <= move_pix + 1:
        return None

    block_size = rng.randint(1, size - move_pix - 1)
    block_pos = rng.randint(0, size - block_size - move_pix)

    if solid:
        color = rng.randint(1, 9)
        block = [color] * block_size
    else:
        block = [rng.randint(1, 9) for _ in range(block_size)]

    question = write_block(block_pos, block, gen_field(size))
    answer = write_block(block_pos + move_pix, block, gen_field(size))

    return {"input": question, "output": answer}


def task_move_n_pix_wrapped(rng: Random, size: int, move_pix: int, solid: bool) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block is moved to the right by move_pix pixels with wrapping."""
    block_size = rng.randint(1, size)
    block_pos = rng.randint(0, size - 1)

    if solid:
        color = rng.randint(1, 9)
        block = [color] * block_size
    else:
        block = [rng.randint(1, 9) for _ in range(block_size)]

    question = gen_field(size)
    answer = gen_field(size)

    for i, color in enumerate(block):
        question[(block_pos + i) % size] = color
        answer[(block_pos + move_pix + i) % size] = color

    return {"input": question, "output": answer}


def task_gravity(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where all non-zero elements are attracted to the left."""
    density = 0.5
    question = [rng.randint(1, 9) if rng.random() < density else 0 for _ in range(size)]

    non_zero = [x for x in question if x != 0]
    answer = non_zero + [0] * (size - len(non_zero))

    return {"input": question, "output": answer}


def task_gravity_counting(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where non-zero elements are counted and represented as a sequence of 1s."""
    density = 0.5
    question = [rng.randint(1, 9) if rng.random() < density else 0 for _ in range(size)]

    count = sum(1 for x in question if x != 0)
    answer = [1] * count + [0] * (size - count)

    return {"input": question, "output": answer}


def task_gravity_antigravity(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where color 1 moves right and color 2 moves left."""
    density = 0.5
    question = [rng.randint(1, 2) if rng.random() < density else 0 for _ in range(size)]

    color1 = [x for x in question if x == 1]
    color2 = [x for x in question if x == 2]
    answer = [2] * len(color2) + [0] * (size - len(color1) - len(color2)) + [1] * len(color1)

    return {"input": question, "output": answer}


def task_block_touch_dot(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block moves to touch (but not cover) a dot."""
    dot_color = 1
    block_color = rng.randint(2, 9)

    block_size = rng.randint(1, size - 1)
    dot_pos = rng.randint(0, size - 1)

    can_place_left = dot_pos >= block_size
    can_place_right = dot_pos + block_size < size

    if not (can_place_left or can_place_right):
        return None

    if can_place_left and can_place_right:
        side = rng.choice(("left", "right"))
    elif can_place_left:
        side = "left"
    else:
        side = "right"

    if side == "left":
        q_block_pos = rng.randint(0, dot_pos - block_size)
        a_block_pos = dot_pos - block_size
    else:
        q_block_pos = rng.randint(dot_pos + 1, size - block_size)
        a_block_pos = dot_pos + 1

    question = gen_field(size)
    question[dot_pos] = dot_color
    question = write_block(q_block_pos, [block_color] * block_size, question)

    answer = gen_field(size)
    answer[dot_pos] = dot_color
    answer = write_block(a_block_pos, [block_color] * block_size, answer)

    return {"input": question, "output": answer}


def task_block_touch_dot_n_pix(rng: Random, size: int, move_pix: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block moves move_pix pixels toward a dot."""
    dot_color = 2
    block_color = rng.randint(3, 9)

    block_size = rng.randint(1, size - 1)
    dot_pos = rng.randint(0, size - 1)

    can_place_left = dot_pos >= block_size
    can_place_right = dot_pos + block_size < size

    if not (can_place_left or can_place_right):
        return None

    if can_place_left and can_place_right:
        side = rng.choice(("left", "right"))
    elif can_place_left:
        side = "left"
    else:
        side = "right"

    if side == "left":
        q_block_pos = rng.randint(0, dot_pos - block_size)
        distance = (dot_pos - block_size) - q_block_pos
        move = min(distance, move_pix)
        a_block_pos = q_block_pos + move
    else:
        q_block_pos = rng.randint(dot_pos + 1, size - block_size)
        distance = q_block_pos - (dot_pos + 1)
        move = min(distance, move_pix)
        a_block_pos = q_block_pos - move

    question = gen_field(size)
    question[dot_pos] = dot_color
    question = write_block(q_block_pos, [block_color] * block_size, question)

    answer = gen_field(size)
    answer[dot_pos] = dot_color
    answer = write_block(a_block_pos, [block_color] * block_size, answer)

    return {"input": question, "output": answer}


def task_block_scale_to_dot(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block scales to touch a dot (keeping one end fixed)."""
    dot_color = 2
    block_color = rng.randint(3, 9)

    block_size = rng.randint(1, size - 1)
    dot_pos = rng.randint(0, size - 1)

    can_place_left = dot_pos >= block_size
    can_place_right = dot_pos + block_size < size

    if not (can_place_left or can_place_right):
        return None

    if can_place_left and can_place_right:
        side = rng.choice(("left", "right"))
    elif can_place_left:
        side = "left"
    else:
        side = "right"

    if side == "left":
        q_block_pos = rng.randint(0, dot_pos - block_size)
        new_size = dot_pos - q_block_pos + 1
        a_block_pos = q_block_pos
    else:
        q_block_pos = rng.randint(dot_pos + 1, size - block_size)
        new_size = (q_block_pos + block_size) - dot_pos
        a_block_pos = dot_pos

    question = gen_field(size)
    question[dot_pos] = dot_color
    question = write_block(q_block_pos, [block_color] * block_size, question)

    answer = gen_field(size)
    answer[dot_pos] = dot_color
    answer = write_block(a_block_pos, [block_color] * new_size, answer)

    return {"input": question, "output": answer}


def task_two_points_and_fill(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where space between two points of same color is filled with that color."""
    color = rng.randint(1, 9)

    pos1 = rng.randint(0, size - 1)
    pos2 = rng.randint(0, size - 1)
    if pos1 == pos2:
        return None

    pos1, pos2 = min(pos1, pos2), max(pos1, pos2)

    question = gen_field(size)
    question[pos1] = color
    question[pos2] = color

    answer = question.copy()
    for i in range(pos1, pos2 + 1):
        answer[i] = color

    return {"input": question, "output": answer}


def task_reflect_block_with_border_pixel(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block with a border pixel is reflected."""
    block_size = rng.randint(2, size)

    c1 = rng.randint(1, 9)
    c2 = rng.choice(tuple(c for c in range(1, 9) if c != c1))

    side = "left" if rng.random() < 0.5 else "right"
    pos = rng.randint(0, size - block_size)

    block = [c1] * block_size
    if side == "left":
        block[0] = c2
    else:
        block[block_size - 1] = c2

    question = write_block(pos, block, gen_field(size))
    reversed_block = block[::-1]  # Reverse the block
    answer = write_block(pos, reversed_block, gen_field(size))

    return {"input": question, "output": answer}


def task_reflect_block_with_border_pixel_random(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a random-colored block with a border pixel is reflected."""
    block_size = rng.randint(2, size)

    side = "left" if rng.random() < 0.5 else "right"
    pos = rng.randint(0, size - block_size)

    border_color = rng.randint(1, 9)
    other_colors = tuple(c for c in range(1, 9) if c != border_color)
    block = [rng.choice(other_colors) for _ in range(block_size)]

    if side == "left":
        block[0] = border_color
    else:
        block[block_size - 1] = border_color

    question = write_block(pos, block, gen_field(size))
    reversed_block = block[::-1]  # Reverse the block
    answer = write_block(pos, reversed_block, gen_field(size))

    return {"input": question, "output": answer}


def task_reflect_block_around_dot(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block is reflected around a dot."""
    dot_color = 2

    dot_pos = rng.randint(0, size - 1)
    block_size = rng.randint(1, size - 1)
    block_pos = rng.randint(0, size - block_size)
    block_end = block_pos + block_size - 1

    # Check if block is strictly to left or right of dot
    strictly_left = block_end < dot_pos
    strictly_right = block_pos > dot_pos

    if not (strictly_left or strictly_right):
        return None

    block_color = rng.randint(3, 9)  # Different from dot color
    block = [block_color] * block_size

    # Calculate reflection bounds
    min_reflect = 2 * dot_pos - block_end
    max_reflect = 2 * dot_pos - block_pos
    if min_reflect < 0 or max_reflect >= size:
        return None

    question = gen_field(size)
    question = write_block(block_pos, block, question)
    question[dot_pos] = dot_color

    answer = gen_field(size)
    answer[dot_pos] = dot_color
    for i in range(block_size):
        reflect_idx = 2 * dot_pos - (block_pos + i)
        answer[reflect_idx] = block[i]

    return {"input": question, "output": answer}


def task_block_and_noise_remove(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where noise around a block needs to be removed."""
    block_size = rng.randint(2, size)

    block_pos = rng.randint(0, size - block_size)
    color = rng.randint(1, 9)

    # Create field with block
    field = gen_field(size)
    for i in range(block_size):
        field[block_pos + i] = color

    # Track forbidden positions for noise
    forbidden = [False] * size
    for i in range(block_pos, block_pos + block_size):
        forbidden[i] = True
    if block_pos > 0:
        forbidden[block_pos - 1] = True
    if block_pos + block_size < size:
        forbidden[block_pos + block_size] = True

    # Add noise
    noise_count = rng.randint(1, 3)
    noise_positions = []

    for _ in range(noise_count):
        allowed = tuple(i for i in range(size) if not forbidden[i])
        if not allowed:
            break
        noise_pos = rng.choice(allowed)
        noise_positions.append(noise_pos)
        field[noise_pos] = color
        forbidden[noise_pos] = True
        if noise_pos > 0:
            forbidden[noise_pos - 1] = True
        if noise_pos + 1 < size:
            forbidden[noise_pos + 1] = True

    if len(noise_positions) < noise_count:
        return None

    question = field
    answer = field.copy()
    for pos in noise_positions:
        answer[pos] = 0

    return {"input": question, "output": answer}


def task_block_and_noise_remove_inside(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where noise inside a block needs to be removed."""
    if size <= 6:
        return None

    block_size = rng.randint(6, size)

    block_pos = rng.randint(0, size - block_size)
    color = rng.randint(1, 9)

    # Create field with block
    field = gen_field(size)
    for i in range(block_size):
        field[block_pos + i] = color

    # Add noise inside block
    max_noise = max(1, (block_size // 2) - 1)
    noise_count = rng.randint(1, max_noise)

    positions = list(range(block_size))
    rng.shuffle(positions)
    noise_positions = positions[:noise_count]

    for offset in noise_positions:
        pos = block_pos + offset
        noise_color = rng.randint(1, 9)
        while noise_color == color:
            noise_color = rng.randint(1, 9)
        field[pos] = noise_color

    question = field
    answer = field.copy()
    for offset in noise_positions:
        answer[block_pos + offset] = color

    return {"input": question, "output": answer}


def task_copy_block_to_dots(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block pattern is copied to dot positions."""
    block_size = 3 if rng.random() < 0.5 else 5
    if block_size >= size:
        return None

    color = rng.randint(1, 9)
    block = [color] * block_size

    # Generate dots with minimum distance to prevent overlap
    min_gap = block_size
    dot_positions = []
    pos = block_size + block_size // 2 + 1

    while pos <= size - block_size:
        if rng.random() < 0.5:  # Control dot density
            dot_positions.append(pos)
            pos += min_gap
        pos += 1

    if not dot_positions:
        return None

    question = gen_field(size)
    question = write_block(0, block, question)
    for pos in dot_positions:
        question[pos] = color

    answer = gen_field(size)
    answer = write_block(0, block, answer)
    for pos in dot_positions:
        block_start = pos - block_size // 2
        answer = write_block(block_start, block, answer)

    return {"input": question, "output": answer}


def task_copy_block_to_dots_colors(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block pattern is copied to dot positions with matching colors."""
    block_size = 3 if rng.random() < 0.5 else 5
    if block_size >= size:
        return None

    block_color = rng.randint(1, 9)
    block = [block_color] * block_size

    # Generate dots with minimum distance to prevent overlap
    min_gap = block_size
    dot_positions = []
    dot_colors = []
    pos = block_size + block_size // 2 + 1

    while pos <= size - block_size:
        if rng.random() < 0.5:
            dot_color = rng.randint(1, 9)
            dot_positions.append(pos)
            dot_colors.append(dot_color)
            pos += min_gap
        pos += 1

    if not dot_positions:
        return None

    question = gen_field(size)
    question = write_block(0, block, question)
    for i, pos in enumerate(dot_positions):
        question[pos] = dot_colors[i]

    answer = gen_field(size)
    answer = write_block(0, block, answer)
    for i, pos in enumerate(dot_positions):
        block_start = pos - block_size // 2
        colored_block = [dot_colors[i]] * block_size
        answer = write_block(block_start, colored_block, answer)

    return {"input": question, "output": answer}


def task_paint_biggest_block(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where the largest block is painted a different color."""
    target_color = 1
    initial_color = rng.randint(2, 9)

    # Generate random blocks
    question = gen_field(size)
    blocks = []
    pos = 0

    while pos < size:
        if rng.random() < 0.4 and size - pos >= 2:
            block_size = rng.randint(2, min(size - pos, 6))
            blocks.append((pos, block_size))
            for i in range(block_size):
                question[pos + i] = initial_color
            pos += block_size + 1
        else:
            pos += 1

    if len(blocks) < 2:
        return None

    # Find biggest block
    biggest_pos, biggest_size = max(blocks, key=lambda x: x[1])

    # Check if there are multiple blocks of the same size
    biggest_count = sum(1 for _, size in blocks if size == biggest_size)
    if biggest_count > 1:
        return None

    answer = question.copy()
    for i in range(biggest_size):
        answer[biggest_pos + i] = target_color

    return {"input": question, "output": answer}


def task_sort_blocks_by_size(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where blocks are sorted by size with 1 pixel gaps."""
    color = rng.randint(1, 9)
    blocks = []
    pos = 0

    # Generate random blocks with random sizes
    while pos < size:
        if rng.random() < 0.4 and size - pos >= 2:
            block_size = rng.randint(1, min(size - pos, 6))
            blocks.append((pos, block_size))
            pos += block_size + rng.randint(1, 4)  # Random gaps
        else:
            pos += 1

    if len(blocks) < 2:
        return None

    # Create input field
    question = gen_field(size)
    for pos, block_size in blocks:
        for i in range(block_size):
            question[pos + i] = color

    # Sort blocks by size
    blocks.sort(key=lambda x: x[1])

    # Check if sorted blocks fit with gaps
    total_space = sum(size for _, size in blocks) + len(blocks) - 1
    if total_space > size:
        return None

    # Create answer field with sorted blocks
    answer = gen_field(size)
    current_pos = 0

    for _, block_size in blocks:
        for i in range(block_size):
            answer[current_pos + i] = color
        current_pos += block_size + 1  # One pixel gap

    return {"input": question, "output": answer}


def task_sort_complete_sequence(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a complete sequence of block sizes is sorted."""
    # Calculate max possible block size given total array size
    max_size = 1
    total_space = 0
    while total_space + max_size + 1 <= size:
        total_space += max_size + 1
        max_size += 1
    max_size -= 1

    if max_size < 2:
        return None

    color = rng.randint(1, 9)

    # Create sequence of all sizes from 1 to max_size
    blocks = list(range(1, max_size + 1))
    rng.shuffle(blocks)

    # Create input field with shuffled blocks
    question = gen_field(size)
    pos = 0
    for block_size in blocks:
        for i in range(block_size):
            question[pos + i] = color
        pos += block_size + 1

    # Create answer field with sorted blocks
    answer = gen_field(size)
    pos = 0
    for block_size in range(1, max_size + 1):
        for i in range(block_size):
            answer[pos + i] = color
        pos += block_size + 1

    return {"input": question, "output": answer}


def task_recolor_blocks_by_size(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where two blocks are recolored based on their size."""
    # Generate two different random sizes
    size1 = rng.randint(2, 8)
    size2 = rng.randint(2, 8)
    while size2 == size1:
        size2 = rng.randint(2, 8)

    # Ensure both blocks fit with at least 1 gap
    if size1 + size2 + 1 > size:
        return None

    # Place blocks with gap
    pos1 = rng.randint(0, size - (size1 + size2 + 1))
    pos2 = rng.randint(pos1 + size1 + 1, size - size2)

    # Create input field with both blocks color 3
    question = gen_field(size)
    for i in range(size1):
        question[pos1 + i] = 3
    for i in range(size2):
        question[pos2 + i] = 3

    # Create answer field with recolored blocks
    answer = question.copy()
    if size1 > size2:
        for i in range(size1):
            answer[pos1 + i] = 1
        for i in range(size2):
            answer[pos2 + i] = 2
    else:
        for i in range(size1):
            answer[pos1 + i] = 2
        for i in range(size2):
            answer[pos2 + i] = 1

    return {"input": question, "output": answer}


def task_gravity_one_step(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where non-zero elements move one step left if possible."""
    question = [rng.randint(1, 9) if rng.random() < 0.5 else 0 for _ in range(size)]
    answer = question.copy()

    # Move each non-zero pixel one step left if possible
    for i in range(1, size):
        if answer[i] != 0 and answer[i - 1] == 0:
            answer[i - 1] = answer[i]
            answer[i] = 0

    return {"input": question, "output": answer}


def task_move_block_by_own_size(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block moves right by its own size."""
    block_size = rng.randint(1, size // 2)  # Ensure space for movement
    pos = rng.randint(0, size - block_size * 2)  # Space for block and movement
    color = rng.randint(1, 9)

    question = gen_field(size)
    block = [color] * block_size
    question = write_block(pos, block, question)

    answer = write_block(pos + block_size, block, gen_field(size))

    return {"input": question, "output": answer}


def task_change_to_five(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where all non-zero colors change to 5."""
    density = 0.5
    question = [rng.randint(1, 9) if rng.random() < density else 0 for _ in range(size)]
    answer = [5 if x != 0 else 0 for x in question]

    return {"input": question, "output": answer}


def task_recolor_blocks_from_palette(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where blocks are recolored using a color palette."""
    # Generate blocks of same size
    block_size = rng.randint(2, 4)
    blocks = []
    pos = 0

    while pos + block_size <= size:
        if rng.random() < 0.4:
            blocks.append(pos)
            pos += block_size + 1
        else:
            pos += 1

    # Ensure we have space for palette
    while blocks and blocks[-1] + block_size + len(blocks) + 1 >= size:
        blocks.pop()

    if not blocks:
        return None

    # Shift blocks right to make room for palette
    palette_size = len(blocks)
    blocks = [pos + palette_size + 1 for pos in blocks]

    # Generate color palette
    colors = []
    for _ in range(len(blocks)):
        while True:
            color = rng.randint(1, 9)
            if color not in colors:
                colors.append(color)
                break

    # Create question with color palette and blocks
    question = gen_field(size)

    # Place color palette at start
    for i, color in enumerate(colors):
        question[i] = color

    # Place blocks of color 5
    for block_pos in blocks:
        for i in range(block_size):
            question[block_pos + i] = 5

    # Create answer with recolored blocks
    answer = question.copy()
    for block_idx, block_pos in enumerate(blocks):
        color = colors[block_idx]
        for i in range(block_size):
            answer[block_pos + i] = color

    return {"input": question, "output": answer}


def task_duplicate_block_from_seeds(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a block is duplicated from seed pixels."""
    block_size = rng.randint(2, 4)
    if block_size + 1 >= size:
        return None
    if size <= 3 + block_size:
        return None

    # Position block with space for seeds
    block_pos = rng.randint(2, size - block_size - 2)

    # Decide seed placement
    left_seed = False
    right_seed = False
    while not left_seed and not right_seed:
        left_seed = rng.random() < 0.5
        right_seed = rng.random() < 0.5

    # Create input
    question = gen_field(size)

    # Place main block
    for i in range(block_size):
        question[block_pos + i] = 1

    # Place seeds with gaps
    seeds = []
    if left_seed:
        color = rng.randint(1, 9)
        question[block_pos - 2] = color
        seeds.append(("left", block_pos - 2, color))
    if right_seed:
        color = rng.randint(1, 9)
        question[block_pos + block_size + 1] = color
        seeds.append(("right", block_pos + block_size + 1, color))

    # Create answer with duplicated blocks
    answer = question.copy()

    for side, seed_pos, color in seeds:
        if side == "left":
            # For left seed, blocks end at seed
            end_pos = seed_pos
            while end_pos >= 0:
                start_pos = end_pos - block_size + 1
                for pos in range(max(0, start_pos), end_pos + 1):
                    answer[pos] = color
                if start_pos < 1:
                    break
                end_pos = start_pos - 2  # -1 for gap
        else:  # side == "right"
            # For right seed, blocks start at seed
            start_pos = seed_pos
            while start_pos < size:
                for offset in range(min(block_size, size - start_pos)):
                    answer[start_pos + offset] = color
                if start_pos + block_size + 1 >= size:
                    break
                start_pos = start_pos + block_size + 1  # +1 for gap

    return {"input": question, "output": answer}


def task_fill_from_pixel(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a pixel fills in one direction until hitting another pixel."""
    if size < 8:
        return None

    block_size = rng.randint(3, size - 5)

    # Position block with space for seed
    block_pos = rng.randint(2, size - block_size - 2)

    # Create input
    question = gen_field(size)

    # Place main block
    block_color = rng.randint(1, 9)
    for i in range(block_size):
        question[block_pos + i] = block_color

    # Place seed pixel and determine fill direction
    seed_color = rng.randint(1, 8)
    if seed_color >= block_color:
        seed_color += 1

    is_left = rng.random() < 0.5

    if is_left:
        question[block_pos - 1] = seed_color
    else:
        question[block_pos + block_size] = seed_color

    # Create answer with fill
    answer = question.copy()

    if is_left:
        # Fill from seed to left border
        for i in range(block_pos):
            answer[i] = seed_color
    else:
        # Fill from seed to right border
        for i in range(block_pos + block_size, size):
            answer[i] = seed_color

    return {"input": question, "output": answer}


def task_mark_size_two_blocks(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where size-2 blocks are marked with surrounding pixels."""
    if size < 8:
        return None

    # Start with one size-2 block
    blocks = [2]
    pos = 4  # Space for first block (2) + gap (2)

    # Generate more blocks
    while pos < size:
        if rng.random() < 0.4:
            block_size = rng.randint(1, 3)
            if pos + block_size <= size:
                blocks.append(block_size)
            pos += block_size + 2  # block + gap
        else:
            blocks.append(0)
            pos += 1

    # Shuffle block sizes
    rng.shuffle(blocks)

    # Assign positions with proper gaps
    block_positions = []
    pos = 0

    for block_size in blocks:
        if block_size == 0:
            pos += 1
        else:
            block_positions.append((pos, block_size))
            pos += block_size + 2  # Move past block + gap

    # Create input with blocks
    question = gen_field(size)
    for pos, block_size in block_positions:
        block_color = rng.randint(1, 8)
        if block_color >= 3:  # avoid marker color 3
            block_color += 1
        for i in range(block_size):
            question[pos + i] = block_color

    # Create answer with markers
    answer = question.copy()
    for pos, block_size in block_positions:
        if block_size == 2:
            if pos > 0:
                answer[pos - 1] = 3
            if pos + block_size < size:
                answer[pos + block_size] = 3

    return {"input": question, "output": answer}


def task_fill_until_collision(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where pixels fill empty space until collision."""
    # At least 4 positions for meaningful puzzle
    if size < 4:
        return None

    is_left = rng.random() < 0.5
    question = gen_field(size)

    # Place the side marker
    if is_left:
        question[0] = 5
    else:
        question[size - 1] = 5

    # Place 2-4 random pixels
    num_pixels = rng.randint(2, 4)
    positions = []

    if is_left:
        # Skip first position
        for _ in range(num_pixels):
            while True:
                pos = rng.randint(1, size - 1)
                if pos not in positions:
                    positions.append(pos)
                    break
    else:
        # Skip last position
        for _ in range(num_pixels):
            while True:
                pos = rng.randint(0, size - 2)
                if pos not in positions:
                    positions.append(pos)
                    break

    # Color random pixels
    for pos in positions:
        c = rng.randint(1, 8)
        if c >= 5:  # don't use side marker color 5
            c += 1
        question[pos] = c

    positions.sort()

    # Create answer
    answer = question.copy()

    if is_left:
        # Fill right from each pixel
        prev_pos = 0  # Start from marker
        for pos in positions:
            color = question[pos]
            # Fill from previous position to current
            for i in range(prev_pos + 1, pos):
                answer[i] = color
            prev_pos = pos
    else:
        # Fill left from each pixel
        prev_pos = size - 1  # Start from marker
        for pos in reversed(positions):
            color = question[pos]
            # Fill from current position to previous
            for i in range(pos + 1, prev_pos):
                answer[i] = color
            prev_pos = pos

    return {"input": question, "output": answer}


def task_repeat_pattern_full(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where a pattern is repeated to fill the space."""
    # Generate initial pattern
    pattern_size = rng.randint(2, 5)
    pattern = [rng.randint(1, 9) for _ in range(pattern_size)]

    # Calculate total size needed for 2 repetitions
    double_size = pattern_size * 2
    if double_size >= size:
        return None

    # Create input with 2 repetitions
    question = gen_field(size)
    for i in range(pattern_size):
        question[i] = pattern[i]
        question[i + pattern_size] = pattern[i]

    # Create answer with maximum repetitions
    answer = gen_field(size)
    pos = 0
    while pos + pattern_size <= size:
        for i in range(pattern_size):
            answer[pos + i] = pattern[i]
        pos += pattern_size

    # Fill remaining space (if any) with pattern elements
    for i in range(pos, size):
        answer[i] = pattern[i - pos]

    return {"input": question, "output": answer}


def task_gravity_weighted_colors(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where color 2 is heavier than color 1 in gravity."""
    # Generate random field with only colors 1 and 2
    question = [rng.randint(1, 2) if rng.random() < 0.5 else 0 for _ in range(size)]

    # Count colors
    count_1 = sum(1 for x in question if x == 1)
    count_2 = sum(1 for x in question if x == 2)

    # Create answer with sorted colors
    answer = gen_field(size)

    # Place heavier color 2 first
    for i in range(count_2):
        answer[i] = 2

    # Then place color 1
    for i in range(count_1):
        answer[count_2 + i] = 1

    return {"input": question, "output": answer}


def task_color_left_half_blocks(rng: Random, size: int) -> Optional[dict[str, list[int]]]:
    """Generate a task where left half of blocks are colored differently."""
    pos = 0
    question = gen_field(size)
    blocks = []

    # Generate blocks with gap 1
    while pos < size:
        if rng.random() < 0.4:
            block_size = rng.randint(2, size // 2)
            if pos + block_size > size:
                break

            blocks.append((pos, block_size))
            for i in range(block_size):
                question[pos + i] = 2
            pos += block_size + 1  # block size + gap
        else:
            pos += 1

    if len(blocks) < 2:
        return None

    # Create answer with half-colored blocks
    answer = question.copy()
    for pos, block_size in blocks:
        half_size = block_size // 2
        for i in range(half_size):
            answer[pos + i] = 8

    return {"input": question, "output": answer}


def task_mirror(task_result: Optional[dict[str, list[int]]]) -> Optional[dict[str, list[int]]]:
    """Mirror the input and output arrays of a task result."""
    if task_result is None:
        return None
    return {"input": list(reversed(task_result["input"])), "output": list(reversed(task_result["output"]))}


def task_inverse(task_result: Optional[dict[str, list[int]]]) -> Optional[dict[str, list[int]]]:
    """Swap the input and output arrays of a task result."""
    if task_result is None:
        return None
    return {"input": task_result["output"], "output": task_result["input"]}


def task_identity(task_result: Optional[dict[str, list[int]]]) -> Optional[dict[str, list[int]]]:
    """Return the task result unchanged."""
    return task_result


# Table of all ARC 1D task functions with their parameters
ARC_1D_TASKS = {
    # Move tasks - right direction
    "move_1pix_solid_right": (task_move_n_pix, {"move_pix": 1, "solid": True}),
    "move_2pix_solid_right": (task_move_n_pix, {"move_pix": 2, "solid": True}),
    "move_3pix_solid_right": (task_move_n_pix, {"move_pix": 3, "solid": True}),
    "move_4pix_solid_right": (task_move_n_pix, {"move_pix": 4, "solid": True}),
    "move_1pix_colorful_right": (task_move_n_pix, {"move_pix": 1, "solid": False}),
    "move_2pix_colorful_right": (task_move_n_pix, {"move_pix": 2, "solid": False}),
    "move_3pix_colorful_right": (task_move_n_pix, {"move_pix": 3, "solid": False}),
    "move_4pix_colorful_right": (task_move_n_pix, {"move_pix": 4, "solid": False}),
    # Move tasks - left direction (mirrored)
    "move_1pix_solid_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 1, "solid": True},
    ),
    "move_2pix_solid_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 2, "solid": True},
    ),
    "move_3pix_solid_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 3, "solid": True},
    ),
    "move_4pix_solid_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 4, "solid": True},
    ),
    "move_1pix_colorful_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 1, "solid": False},
    ),
    "move_2pix_colorful_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 2, "solid": False},
    ),
    "move_3pix_colorful_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 3, "solid": False},
    ),
    "move_4pix_colorful_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix(rng, size, **kwargs)),
        {"move_pix": 4, "solid": False},
    ),
    # Move wrapped tasks - right direction
    "move_1pix_solid_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 1, "solid": True}),
    "move_2pix_solid_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 2, "solid": True}),
    "move_3pix_solid_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 3, "solid": True}),
    "move_4pix_solid_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 4, "solid": True}),
    "move_1pix_colorful_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 1, "solid": False}),
    "move_2pix_colorful_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 2, "solid": False}),
    "move_3pix_colorful_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 3, "solid": False}),
    "move_4pix_colorful_wrapped_right": (task_move_n_pix_wrapped, {"move_pix": 4, "solid": False}),
    # Move wrapped tasks - left direction (mirrored)
    "move_1pix_solid_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 1, "solid": True},
    ),
    "move_2pix_solid_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 2, "solid": True},
    ),
    "move_3pix_solid_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 3, "solid": True},
    ),
    "move_4pix_solid_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 4, "solid": True},
    ),
    "move_1pix_colorful_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 1, "solid": False},
    ),
    "move_2pix_colorful_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 2, "solid": False},
    ),
    "move_3pix_colorful_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 3, "solid": False},
    ),
    "move_4pix_colorful_wrapped_left": (
        lambda rng, size, **kwargs: task_mirror(task_move_n_pix_wrapped(rng, size, **kwargs)),
        {"move_pix": 4, "solid": False},
    ),
    # Gravity tasks - right direction
    "gravity_right": (task_gravity, {}),
    "gravity_counting_right": (task_gravity_counting, {}),
    "gravity_antigravity_right": (task_gravity_antigravity, {}),
    "gravity_one_step_right": (task_gravity_one_step, {}),
    "gravity_weighted_colors_right": (task_gravity_weighted_colors, {}),
    # Gravity tasks - left direction (mirrored)
    "gravity_left": (lambda rng, size, **kwargs: task_mirror(task_gravity(rng, size, **kwargs)), {}),
    "gravity_counting_left": (lambda rng, size, **kwargs: task_mirror(task_gravity_counting(rng, size, **kwargs)), {}),
    "gravity_antigravity_left": (
        lambda rng, size, **kwargs: task_mirror(task_gravity_antigravity(rng, size, **kwargs)),
        {},
    ),
    "gravity_one_step_left": (lambda rng, size, **kwargs: task_mirror(task_gravity_one_step(rng, size, **kwargs)), {}),
    "gravity_weighted_colors_left": (
        lambda rng, size, **kwargs: task_mirror(task_gravity_weighted_colors(rng, size, **kwargs)),
        {},
    ),
    # Block tasks
    "block_touch_dot": (task_block_touch_dot, {}),
    "block_touch_dot_1pix": (task_block_touch_dot_n_pix, {"move_pix": 1}),
    "block_touch_dot_2pix": (task_block_touch_dot_n_pix, {"move_pix": 2}),
    "block_touch_dot_3pix": (task_block_touch_dot_n_pix, {"move_pix": 3}),
    "block_touch_dot_4pix": (task_block_touch_dot_n_pix, {"move_pix": 4}),
    "block_scale_to_dot": (task_block_scale_to_dot, {}),
    "block_and_noise_remove": (task_block_and_noise_remove, {}),
    "block_and_noise_remove_inside": (task_block_and_noise_remove_inside, {}),
    "move_block_by_own_size": (task_move_block_by_own_size, {}),
    # Pattern tasks
    "two_points_and_fill": (task_two_points_and_fill, {}),
    "two_points_and_fill_inv": (
        lambda rng, size, **kwargs: task_inverse(task_two_points_and_fill(rng, size, **kwargs)),
        {},
    ),
    "copy_block_to_dots": (task_copy_block_to_dots, {}),
    "copy_block_to_dots_colors": (task_copy_block_to_dots_colors, {}),
    "repeat_pattern_full": (task_repeat_pattern_full, {}),
    # Reflection tasks
    "reflect_block_with_border_pixel": (task_reflect_block_with_border_pixel, {}),
    "reflect_block_random": (task_reflect_block_with_border_pixel_random, {}),
    "reflect_block_around_dot": (task_reflect_block_around_dot, {}),
    # Color tasks
    "paint_biggest_block": (task_paint_biggest_block, {}),
    "recolor_blocks_by_size": (task_recolor_blocks_by_size, {}),
    "change_to_five": (task_change_to_five, {}),
    "recolor_blocks_from_palette": (task_recolor_blocks_from_palette, {}),
    "color_left_half_blocks": (task_color_left_half_blocks, {}),
    # Sorting tasks
    "sort_blocks_by_size": (task_sort_blocks_by_size, {}),
    "sort_complete_sequence": (task_sort_complete_sequence, {}),
    # Fill tasks
    "duplicate_block_from_seeds": (task_duplicate_block_from_seeds, {}),
    "fill_from_pixel": (task_fill_from_pixel, {}),
    "fill_until_collision": (task_fill_until_collision, {}),
    # Marking tasks
    "mark_size_two_blocks": (task_mark_size_two_blocks, {}),
}
