from __future__ import annotations

from pathlib import Path

from PIL import Image, ImageChops, ImageDraw, ImageEnhance, ImageFilter


FRAME_SIZE = 313
ROOT = Path(__file__).resolve().parents[1]
UNIT_DIR = ROOT / "src" / "assets" / "images" / "units"

DIRECTIONS = ("south", "east", "north", "west")
FRAME_BY_ACTION = {
    "attack": 2,
    "strategy": 0,
    "item": 1,
    "hurt": 0,
}

FORWARD = {
    "south": (0, 15),
    "east": (18, 0),
    "north": (0, -10),
    "west": (-18, 0),
}

BACKWARD = {
    "south": (0, -12),
    "east": (-14, 0),
    "north": (0, 12),
    "west": (14, 0),
}

ITEM_POS = {
    "south": (184, 142),
    "east": (222, 143),
    "north": (134, 136),
    "west": (88, 143),
}

ACTION_ORDER = ("attack", "strategy", "item", "hurt")


def main() -> None:
    for source in sorted(UNIT_DIR.glob("unit-*.png")):
        if source.name.endswith("-actions.png"):
            continue
        sheet = Image.open(source).convert("RGBA")
        output = Image.new("RGBA", (FRAME_SIZE * len(ACTION_ORDER), FRAME_SIZE * len(DIRECTIONS)), (0, 0, 0, 0))

        for row, direction in enumerate(DIRECTIONS):
            for col, action in enumerate(ACTION_ORDER):
                frame = crop_frame(sheet, row, FRAME_BY_ACTION[action])
                if action == "attack":
                    action_frame = make_attack(frame, direction)
                elif action == "strategy":
                    action_frame = make_strategy(frame, direction)
                elif action == "item":
                    action_frame = make_item(frame, direction)
                else:
                    action_frame = make_hurt(frame, direction)
                output.alpha_composite(action_frame, (col * FRAME_SIZE, row * FRAME_SIZE))

        output.save(source.with_name(f"{source.stem}-actions.png"))


def crop_frame(sheet: Image.Image, row: int, col: int) -> Image.Image:
    return sheet.crop((col * FRAME_SIZE, row * FRAME_SIZE, (col + 1) * FRAME_SIZE, (row + 1) * FRAME_SIZE)).copy()


def offset_subject(frame: Image.Image, dx: int, dy: int) -> Image.Image:
    canvas = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    bbox = frame.getbbox()
    if not bbox:
        return frame
    subject = frame.crop(bbox)
    canvas.alpha_composite(subject, (bbox[0] + dx, bbox[1] + dy))
    return canvas


def underlay(frame: Image.Image, color: tuple[int, int, int, int], blur: int, expand: int = 0) -> Image.Image:
    alpha = frame.getchannel("A")
    if expand > 0:
        alpha = alpha.filter(ImageFilter.MaxFilter(expand * 2 + 1))
    alpha = alpha.filter(ImageFilter.GaussianBlur(blur))
    glow = Image.new("RGBA", frame.size, color)
    glow.putalpha(alpha.point(lambda value: min(255, int(value * color[3] / 255))))
    result = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    result.alpha_composite(glow)
    result.alpha_composite(frame)
    return result


def make_attack(frame: Image.Image, direction: str) -> Image.Image:
    dx, dy = FORWARD[direction]
    body = offset_subject(frame, dx, dy)
    body = underlay(body, (255, 224, 120, 92), 4, 1)
    slash = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(slash)

    if direction == "east":
        points = [(166, 76), (264, 126), (234, 220)]
    elif direction == "west":
        points = [(147, 76), (49, 126), (79, 220)]
    elif direction == "south":
        points = [(74, 104), (166, 184), (248, 112)]
    else:
        points = [(74, 202), (166, 112), (248, 198)]

    draw.line(points, fill=(255, 247, 214, 218), width=15, joint="curve")
    draw.line(points, fill=(98, 210, 255, 178), width=6, joint="curve")
    draw.line(points, fill=(255, 178, 86, 158), width=2, joint="curve")
    slash = slash.filter(ImageFilter.GaussianBlur(0.45))

    result = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    result.alpha_composite(slash)
    result.alpha_composite(body)
    return result


def make_strategy(frame: Image.Image, direction: str) -> Image.Image:
    aura = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(aura)
    center = (156, 160)

    for radius, alpha in ((92, 42), (66, 66), (38, 92)):
        draw.ellipse(
            (center[0] - radius, center[1] - radius, center[0] + radius, center[1] + radius),
            outline=(98, 195, 255, alpha),
            width=4,
        )

    for index in range(10):
        x = 72 + (index * 23) % 168
        y = 58 + (index * 47) % 188
        draw.polygon(
            [(x, y - 7), (x + 4, y), (x, y + 7), (x - 4, y)],
            fill=(255, 230, 132, 140),
        )

    aura = aura.filter(ImageFilter.GaussianBlur(0.35))
    body = underlay(frame, (60, 160, 255, 92), 6, 2)
    result = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    result.alpha_composite(aura)
    result.alpha_composite(body)
    return result


def make_item(frame: Image.Image, direction: str) -> Image.Image:
    body = underlay(frame, (95, 230, 150, 70), 4, 1)
    effect = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(effect)
    x, y = ITEM_POS[direction]

    draw.rounded_rectangle((x - 18, y - 15, x + 18, y + 17), radius=7, fill=(137, 91, 45, 235), outline=(245, 207, 122, 230), width=3)
    draw.ellipse((x - 10, y - 28, x + 10, y - 9), fill=(95, 221, 142, 210), outline=(224, 255, 219, 230), width=2)
    draw.line((x - 26, y + 2, x + 26, y + 2), fill=(224, 255, 219, 184), width=4)
    draw.line((x, y - 24, x, y + 27), fill=(224, 255, 219, 184), width=4)

    for px, py in ((x - 38, y - 30), (x + 36, y - 26), (x - 30, y + 36), (x + 28, y + 34)):
        draw.ellipse((px - 4, py - 4, px + 4, py + 4), fill=(159, 255, 200, 160))

    result = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    result.alpha_composite(body)
    result.alpha_composite(effect)
    return result


def make_hurt(frame: Image.Image, direction: str) -> Image.Image:
    dx, dy = BACKWARD[direction]
    body = offset_subject(frame, dx, dy)
    body = ImageEnhance.Color(body).enhance(0.62)

    alpha = body.getchannel("A")
    red = Image.new("RGBA", body.size, (255, 78, 60, 70))
    red.putalpha(alpha.point(lambda value: min(88, value // 3)))
    body = Image.alpha_composite(body, red)

    impact = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(impact)
    if direction in ("west", "north"):
        cx, cy = 190, 118
    else:
        cx, cy = 124, 118
    spikes = [(cx, cy - 28), (cx + 9, cy - 8), (cx + 31, cy - 6), (cx + 12, cy + 7), (cx + 20, cy + 28), (cx, cy + 13), (cx - 22, cy + 27), (cx - 12, cy + 6), (cx - 31, cy - 7), (cx - 8, cy - 9)]
    draw.polygon(spikes, fill=(255, 222, 118, 194), outline=(255, 84, 57, 224))
    draw.line((cx - 38, cy - 35, cx + 38, cy + 35), fill=(255, 245, 205, 186), width=5)
    draw.line((cx + 34, cy - 32, cx - 32, cy + 34), fill=(255, 245, 205, 154), width=3)

    result = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    result.alpha_composite(body)
    result.alpha_composite(impact.filter(ImageFilter.GaussianBlur(0.2)))
    return result


if __name__ == "__main__":
    main()
