# Canonical blockstates and a deliberately conservative physical model.
import json
import re
from functools import lru_cache
from pathlib import Path

SCHEMA = json.loads((Path(__file__).parent / 'data' / 'blockstates-1.21.1.json').read_text())
AIR = 'minecraft:air'
LIGHTS = {'lantern': 15, 'torch': 14, 'wall_torch': 14, 'glowstone': 15, 'sea_lantern': 15, 'shroomlight': 15, 'ochre_froglight': 15, 'pearlescent_froglight': 15, 'verdant_froglight': 15}
SOFT = {'air', 'cave_air', 'water', 'short_grass', 'fern', 'dead_bush', 'dandelion', 'poppy', 'cornflower', 'allium', 'azure_bluet', 'oxeye_daisy', 'lily_of_the_valley', 'blue_orchid', 'torch', 'wall_torch', 'vine', 'snow', 'lily_pad', 'tall_grass', 'large_fern', 'sunflower', 'rose_bush', 'peony', 'lilac', 'wheat', 'carrots', 'potatoes'}


@lru_cache(maxsize=4096)
def parse(state: str):
    match = re.fullmatch(r'(minecraft:[a-z0-9_]+)(?:\[([^\]]+)\])?', state)
    if not match:
        raise ValueError(f'Invalid blockstate syntax: {state}')
    props = {}
    if match[2]:
        for part in match[2].split(','):
            key, value = part.split('=')
            if key in props:
                raise ValueError('Duplicate block property')
            props[key] = value
    return match[1], props


def state_of(name: str, **properties) -> str:
    if not name.startswith('minecraft:'):
        name = 'minecraft:' + name
    return name + ('[' + ','.join(f'{k}={str(v).lower()}' for k, v in sorted(properties.items())) + ']' if properties else '')


def canonical(state: str) -> str:
    name, props = parse(state)
    return state_of(name, **props)


def blockstates_equivalent(left: str, right: str) -> bool:
    return parse(left) == parse(right)


@lru_cache(maxsize=4096)
def validate_state(state: str):
    name, props = parse(state)
    short = name.removeprefix('minecraft:')
    if short not in SCHEMA:
        raise ValueError(f'Unknown Java 1.21.1 block: {name}')
    domain = SCHEMA[short][0]
    for key, value in props.items():
        if key not in domain or value not in domain[key]:
            raise ValueError(f'Invalid property {key}={value} for {name}')


@lru_cache(maxsize=4096)
def name_of(state: str) -> str:
    return parse(state)[0].removeprefix('minecraft:')


def solid(state: str) -> bool:
    name = name_of(state)
    return name not in SOFT and not any(s in name for s in ('leaves', 'fence', 'pane', 'door', 'lantern', 'chain', 'flower_pot', 'potted_', 'carpet', 'bed', 'button'))


def passable(state: str) -> bool:
    name = name_of(state)
    return name in SOFT - {'water'} or name.endswith('_door') or name.endswith('_carpet')


def stairs(material='stone_brick', facing='north', half='bottom'):
    return state_of(material + '_stairs', facing=facing, half=half, shape='straight', waterlogged='false')


def slab(material='stone_brick', half='bottom'):
    return state_of(material + '_slab', type=half, waterlogged='false')


def log(material='spruce', axis='y'):
    return state_of(material + '_log', axis=axis)


def leaves(material='spruce'):
    return state_of(material + '_leaves', distance='1', persistent='true', waterlogged='false')
