import json
import os
import shutil

import regex as re
from ruamel.yaml import YAML

from dateparser_scripts.order_languages import avoid_languages
from dateparser_scripts.utils import combine_dicts

cldr_date_directory = "../dateparser_data/cldr_language_data/date_translation_data/"
supplementary_directory = "../dateparser_data/supplementary_language_data/"
supplementary_date_directory = (
    "../dateparser_data/supplementary_language_data/date_translation_data/"
)
translation_data_directory = "../dateparser/data/"
date_translation_directory = "../dateparser/data/date_translation_data/"

os.chdir(os.path.dirname(os.path.abspath(__file__)))

cldr_languages = list(
    set(map(lambda x: x[:-5], os.listdir(cldr_date_directory))) - avoid_languages
)
supplementary_languages = [x[:-5] for x in os.listdir(supplementary_date_directory)]
all_languages = set(cldr_languages).union(set(supplementary_languages))

RELATIVE_PATTERN = re.compile(r"\{0\}")
POSSESSIVE_DIGIT_PATTERN = re.compile(r"\\d\+\[\.,\]\?\\d\*")


def _make_possessive(s):
    return POSSESSIVE_DIGIT_PATTERN.sub(r"\\d++[.,]?\\d*+", s)


def _to_plain_types(obj):
    """Recursively convert ruamel.yaml CommentedMap/CommentedSeq to plain
    dict/list so that json.dumps produces stable output across all
    Python versions.

    Python 3.14 changed the json C encoder to bypass the Python-level
    ``__iter__``/``items()`` of dict subclasses and access the underlying C
    dict directly.  ruamel.yaml's CommentedMap relies on its Python-level
    iteration for correct key ordering, so the C shortcut produces a
    different (non-deterministic) key order on 3.14.  Converting to plain
    types before serialisation avoids the issue entirely.
    """
    if isinstance(obj, dict):
        return {k: _to_plain_types(v) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [_to_plain_types(v) for v in obj]
    return obj


def _modify_relative_data(relative_data):
    modified_relative_data = {}
    for key, value in relative_data.items():
        for i, string in enumerate(value):
            string = RELATIVE_PATTERN.sub(r"(\\d++[.,]?\\d*+)", string)
            string = _make_possessive(string)
            value[i] = string
        modified_relative_data[key] = value
    return modified_relative_data


def _modify_simplifications(simplifications):
    for simplification in simplifications:
        for pattern in list(simplification.keys()):
            new_pattern = _make_possessive(pattern)
            if new_pattern != pattern:
                simplification[new_pattern] = simplification.pop(pattern)


def _modify_data(language_data):
    relative_data = language_data.get("relative-type-regex", {})
    relative_data = _modify_relative_data(relative_data)
    simplifications = language_data.get("simplifications", [])
    _modify_simplifications(simplifications)
    locale_specific_data = language_data.get("locale_specific", {})
    for _, info in locale_specific_data.items():
        locale_relative_data = info.get("relative-type-regex", {})
        locale_relative_data = _modify_relative_data(locale_relative_data)


def _get_complete_date_translation_data(language):
    cldr_data = {}
    supplementary_data = {}
    if language in cldr_languages:
        with open(cldr_date_directory + language + ".json") as f:
            cldr_data = json.load(f)
    if language in supplementary_languages:
        with open(supplementary_date_directory + language + ".yaml") as g:
            yaml = YAML()
            supplementary_data = dict(yaml.load(g))
    complete_data = combine_dicts(cldr_data, supplementary_data)
    if "name" not in complete_data:
        complete_data["name"] = language
    return complete_data


def _write_file(filename, text, mode, in_memory, in_memory_result):
    if in_memory:
        in_memory_result[filename] = text
    else:
        with open(filename, mode) as out:
            out.write(text)


def write_complete_data(in_memory=False):
    """
    This function is responsible of generating the needed py files from the
    CLDR files (JSON format) and supplementary language data (YAML format).

    Use it with in_memory=True to avoid writing real files and getting a
    dictionary containing the file names and their content (used when testing).
    """
    in_memory_result = {}

    if not in_memory:
        if not os.path.isdir(translation_data_directory):
            os.mkdir(translation_data_directory)
        if os.path.isdir(date_translation_directory):
            shutil.rmtree(date_translation_directory)
        os.mkdir(date_translation_directory)

    with open(supplementary_directory + "base_data.yaml") as f:
        yaml = YAML()
        base_data = yaml.load(f)

    for language in all_languages:
        date_translation_data = _get_complete_date_translation_data(language)
        date_translation_data = combine_dicts(date_translation_data, base_data)
        date_translation_data = _to_plain_types(date_translation_data)
        _modify_data(date_translation_data)
        translation_data = json.dumps(
            date_translation_data, indent=4, separators=(",", ": "), ensure_ascii=False
        )
        out_text = ("info = " + translation_data + "\n").encode("utf-8")
        _write_file(
            date_translation_directory + language + ".py",
            out_text,
            "wb",
            in_memory,
            in_memory_result,
        )

    init_text = (
        "from dateparser.data import date_translation_data\n"
        "from .languages_info import language_order, language_locale_dict\n"
    )

    _write_file(
        translation_data_directory + "__init__.py",
        init_text,
        "w",
        False,
        in_memory_result,
    )
    _write_file(
        date_translation_directory + "__init__.py", "", "w", False, in_memory_result
    )

    return in_memory_result


if __name__ == "__main__":
    write_complete_data()
