"""
TextileTag v01 - Python SDK
Encode, decode, and validate 14-character TextileTag codes.
"""

GARMENT_TYPES = {
    "TP": "Top", "BT": "Bottom", "UW": "Underwear", "SK": "Socks",
    "OW": "Outerwear", "DR": "Dress", "SW": "Swimwear", "AT": "Athletic",
    "SL": "Sleepwear", "SU": "Suit/Formal", "AC": "Accessory",
    "LN": "Linen/Home", "BG": "Baby/Child", "UN": "Uniform",
    "DN": "Denim", "KN": "Knitwear", "XX": "Other",
}

MATERIALS = {
    "CO": "Cotton", "PL": "Polyester", "SI": "Silk", "WO": "Wool",
    "LI": "Linen", "NY": "Nylon", "RY": "Rayon", "SP": "Spandex",
    "CS": "Cashmere", "DN": "Denim", "FL": "Fleece", "LT": "Leather",
    "SD": "Suede", "VL": "Velvet", "ST": "Satin", "TW": "Tweed",
    "CR": "Corduroy", "CH": "Chiffon", "LC": "Lace", "TL": "Tulle",
    "MB": "Microfiber", "BM": "Bamboo", "HM": "Hemp",
    "CP": "Cotton-Poly", "CL": "Cotton-Lycra", "WS": "Wool-Silk",
    "BL": "Blend", "SY": "Synthetic", "OR": "Organic", "XX": "Other",
}

WASH_CYCLES = {
    "D": "Delicate", "N": "Normal", "H": "Heavy Duty",
    "P": "Permanent Press", "W": "Hand Wash", "X": "Do Not Wash",
}

TEMPERATURES = {
    "C": "Cold", "W": "Warm", "H": "Hot",
    "V": "Very Hot", "X": "No Water",
}

DRY_METHODS = {
    "T": "Tumble Dry", "L": "Tumble Low", "H": "Hang Dry",
    "F": "Flat Dry", "D": "Drip Dry", "X": "No Dryer",
}

COLORS = {
    "W": "White", "L": "Light", "D": "Dark",
    "B": "Bright", "N": "Neutral", "M": "Multi-color",
}

SIZES = {
    "0": "One Size", "1": "XS", "2": "S", "3": "M", "4": "L",
    "5": "XL", "6": "XXL", "7": "XXXL", "8": "XXXXL+",
    "9": "Special", "A": "Petite", "B": "Tall", "C": "Plus", "X": "N/A",
}

IRON_LEVELS = ["No Iron", "Low", "Medium", "High"]
BLEACH_LEVELS = ["No Bleach", "Non-Chlorine Only", "Any Bleach OK"]
DRYCLEAN_OPTIONS = ["No", "Yes"]

_FLAGS = "0123456789ABCDEFGHIJKLMN"


class TextileTagError(Exception):
    pass


def encode_flags(iron: int, bleach: int, dryclean: int) -> str:
    """Encode iron (0-3), bleach (0-2), dryclean (0-1) into a single character."""
    if iron not in range(4):
        raise TextileTagError(f"Iron must be 0-3, got {iron}")
    if bleach not in range(3):
        raise TextileTagError(f"Bleach must be 0-2, got {bleach}")
    if dryclean not in range(2):
        raise TextileTagError(f"Dryclean must be 0-1, got {dryclean}")
    return _FLAGS[(iron * 6) + (bleach * 2) + dryclean]


def decode_flags(char: str) -> dict:
    """Decode a care flags character into iron, bleach, dryclean values."""
    idx = _FLAGS.find(char)
    if idx < 0:
        raise TextileTagError(f"Invalid care flags character: {char}")
    return {
        "iron": idx // 6,
        "iron_label": IRON_LEVELS[idx // 6],
        "bleach": (idx % 6) // 2,
        "bleach_label": BLEACH_LEVELS[(idx % 6) // 2],
        "dryclean": idx % 2,
        "dryclean_label": DRYCLEAN_OPTIONS[idx % 2],
    }


def encode(
    garment_type: str,
    material: str,
    wash: str,
    temp: str,
    dry: str,
    color: str,
    size: str,
    iron: int,
    bleach: int,
    dryclean: int,
    country: str,
    version: str = "01",
) -> str:
    """
    Encode garment attributes into a 14-character TextileTag string.

    Args:
        garment_type: 2-letter code (e.g., "TP", "BT")
        material: 2-letter code (e.g., "CO", "PL")
        wash: 1-letter wash cycle (e.g., "N", "D")
        temp: 1-letter temperature (e.g., "C", "W")
        dry: 1-letter dry method (e.g., "T", "H")
        color: 1-letter color category (e.g., "W", "D")
        size: 1-character size (e.g., "3", "5")
        iron: Iron level 0-3 (none, low, medium, high)
        bleach: Bleach level 0-2 (none, non-chlorine, any)
        dryclean: 0 or 1
        country: 2-letter ISO 3166-1 alpha-2 code
        version: 2-digit version string (default "01")

    Returns:
        14-character TextileTag string
    """
    errors = validate_fields(
        garment_type=garment_type, material=material, wash=wash,
        temp=temp, dry=dry, color=color, size=size, country=country,
        version=version,
    )
    if errors:
        raise TextileTagError("; ".join(errors))

    flag = encode_flags(iron, bleach, dryclean)
    code = version + garment_type + material + wash + temp + dry + color + size + flag + country
    return code


def decode(code: str) -> dict:
    """
    Decode a 14-character TextileTag string into its component fields.

    Args:
        code: 14-character TextileTag string

    Returns:
        Dictionary with all decoded fields and their labels
    """
    errors = validate(code)
    if errors:
        raise TextileTagError("; ".join(errors))

    code = code.upper().replace(" ", "")
    flags = decode_flags(code[11])

    return {
        "code": code,
        "version": code[0:2],
        "garment_type": code[2:4],
        "garment_type_label": GARMENT_TYPES.get(code[2:4], "Unknown"),
        "material": code[4:6],
        "material_label": MATERIALS.get(code[4:6], "Unknown"),
        "wash": code[6],
        "wash_label": WASH_CYCLES.get(code[6], "Unknown"),
        "temp": code[7],
        "temp_label": TEMPERATURES.get(code[7], "Unknown"),
        "dry": code[8],
        "dry_label": DRY_METHODS.get(code[8], "Unknown"),
        "color": code[9],
        "color_label": COLORS.get(code[9], "Unknown"),
        "size": code[10],
        "size_label": SIZES.get(code[10], "Unknown"),
        "iron": flags["iron"],
        "iron_label": flags["iron_label"],
        "bleach": flags["bleach"],
        "bleach_label": flags["bleach_label"],
        "dryclean": flags["dryclean"],
        "dryclean_label": flags["dryclean_label"],
        "country": code[12:14],
    }


def validate(code: str) -> list:
    """
    Validate a TextileTag code string.

    Args:
        code: String to validate

    Returns:
        List of error messages (empty list if valid)
    """
    errors = []
    if not isinstance(code, str):
        return ["Code must be a string"]

    code = code.upper().replace(" ", "")

    if len(code) != 14:
        errors.append(f"Must be 14 characters, got {len(code)}")
        return errors

    if not code.isalnum() or not all(c.isalpha() or c.isdigit() for c in code):
        errors.append("Only A-Z and 0-9 allowed")

    if not code[:2].isdigit():
        errors.append(f"Version must be 2 digits, got '{code[:2]}'")
    elif code[:2] == "00":
        errors.append("Version 00 is reserved/invalid")

    if code[2:4] not in GARMENT_TYPES:
        errors.append(f"Unknown garment type: {code[2:4]}")
    if code[4:6] not in MATERIALS:
        errors.append(f"Unknown material: {code[4:6]}")
    if code[6] not in WASH_CYCLES:
        errors.append(f"Unknown wash cycle: {code[6]}")
    if code[7] not in TEMPERATURES:
        errors.append(f"Unknown temperature: {code[7]}")
    if code[8] not in DRY_METHODS:
        errors.append(f"Unknown dry method: {code[8]}")
    if code[9] not in COLORS:
        errors.append(f"Unknown color: {code[9]}")
    if code[10] not in SIZES:
        errors.append(f"Unknown size: {code[10]}")
    if code[11] not in _FLAGS:
        errors.append(f"Invalid care flags: {code[11]}")

    return errors


def validate_fields(**fields) -> list:
    """Validate individual field values before encoding."""
    errors = []
    if "version" in fields:
        v = fields["version"]
        if not (isinstance(v, str) and len(v) == 2 and v.isdigit() and v != "00"):
            errors.append(f"Invalid version: {v}")
    if "garment_type" in fields and fields["garment_type"] not in GARMENT_TYPES:
        errors.append(f"Unknown garment type: {fields['garment_type']}")
    if "material" in fields and fields["material"] not in MATERIALS:
        errors.append(f"Unknown material: {fields['material']}")
    if "wash" in fields and fields["wash"] not in WASH_CYCLES:
        errors.append(f"Unknown wash cycle: {fields['wash']}")
    if "temp" in fields and fields["temp"] not in TEMPERATURES:
        errors.append(f"Unknown temperature: {fields['temp']}")
    if "dry" in fields and fields["dry"] not in DRY_METHODS:
        errors.append(f"Unknown dry method: {fields['dry']}")
    if "color" in fields and fields["color"] not in COLORS:
        errors.append(f"Unknown color: {fields['color']}")
    if "size" in fields and fields["size"] not in SIZES:
        errors.append(f"Unknown size: {fields['size']}")
    if "country" in fields:
        c = fields["country"]
        if not (isinstance(c, str) and len(c) == 2 and c.isalpha()):
            errors.append(f"Invalid country code: {c}")
    return errors


def describe(code: str) -> str:
    """
    Return a human-readable description of a TextileTag code.

    Args:
        code: 14-character TextileTag string

    Returns:
        Human-readable description string
    """
    d = decode(code)
    parts = [
        f"{d['size_label']} {d['color_label'].lower()} {d['material_label'].lower()} {d['garment_type_label'].lower()}.",
        f"Wash: {d['wash_label']}, {d['temp_label'].lower()} water.",
        f"Dry: {d['dry_label']}.",
        f"Iron: {d['iron_label']}.",
        f"Bleach: {d['bleach_label']}.",
        f"Dry clean: {d['dryclean_label']}.",
        f"Made in: {d['country']}.",
    ]
    return " ".join(parts)


if __name__ == "__main__":
    # Demo
    print("=== TextileTag Python SDK Demo ===\n")

    # Encode
    code = encode(
        garment_type="TP", material="CO", wash="N", temp="C",
        dry="T", color="W", size="3", iron=1, bleach=2,
        dryclean=0, country="US",
    )
    print(f"Encoded: {code}")
    print(f"Describe: {describe(code)}")
    print()

    # Decode
    result = decode("01BTDNNCLD46CN")
    print(f"Decoded: {result['code']}")
    for key in ["garment_type_label", "material_label", "wash_label", "temp_label",
                "dry_label", "color_label", "size_label", "iron_label",
                "bleach_label", "dryclean_label", "country"]:
        print(f"  {key}: {result[key]}")
    print()

    # Validate
    errors = validate("INVALID")
    print(f"Validate 'INVALID': {errors}")
    errors = validate("01TPCONCTW3AUS")
    print(f"Validate '01TPCONCTW3AUS': {'Valid!' if not errors else errors}")
