import datetime
import os
from pathlib import Path
import threading
import yaml
import jinja2
import functools
import markdown


ERROR_LEADER = "[[red bold]FAIL[/bold red]] "
WARN_LEADER = "[[yellow bold]WARN[/bold yellow]] "
INFO_LEADER = "[[turquoise2 bold]info[/turquoise2 bold]] "


def load_yaml(data: str) -> any:
    return yaml.load(data, Loader=yaml.FullLoader)


def is_directory_empty(path: str | Path) -> bool:
    return len(os.listdir(path)) == 0


def extract_frontmatter(instr: str) -> tuple[any, str]:
    first = instr.find("---")
    second = instr.find("---", first + 1)

    if first != 0 or second == -1:
        raise ValueError("no valid frontmatter found")

    end = second + 3

    raw_frontmatter = instr[:end]
    trailer = instr[end:]

    if trailer.startswith("\n"):
        trailer = trailer[1:]

    frontmatter = load_yaml(raw_frontmatter.strip("-\n"))
    return frontmatter, trailer


def get_absolute_url(site_config: any, path: str):
    bu = site_config["baseURL"]
    return bu.rstrip("/") + "/" + path.lstrip("/")


def date_to_datetime(d: datetime.date) -> datetime.datetime:
    return datetime.datetime(d.year, d.month, d.day)


def build_jinja_env(site_config: any) -> jinja2.Environment:
    jinja_env = jinja2.Environment(
        loader=jinja2.FileSystemLoader("site/templates"), autoescape=jinja2.select_autoescape(),
    )
    
    jinja_env.filters["absurl"] = functools.partial(get_absolute_url, site_config)
    jinja_env.filters["fmtdate"] = lambda x: x.strftime("%Y-%m-%d")
    jinja_env.filters["render_md"] = markdown.new_simple_renderer()
    
    return jinja_env


def debounce(wait_time):
    """
    https://stackoverflow.com/a/66907107

    Decorator that will debounce a function so that it is called after wait_time seconds
    If it is called multiple times, will wait for the last call to be debounced and run only this one.
    """

    def decorator(function):
        def debounced(*args, **kwargs):
            def call_function():
                debounced._timer = None
                return function(*args, **kwargs)

            # if we already have a call to the function currently waiting to be executed, reset the timer
            if debounced._timer is not None:
                debounced._timer.cancel()

            # after wait_time, call the function provided to the decorator with its arguments
            debounced._timer = threading.Timer(wait_time, call_function)
            debounced._timer.start()

        debounced._timer = None
        return debounced

    return decorator


class DataLoader:
    _base_path: Path
    _store: dict[str, any]

    def __init__(self, base_path: str | Path):
        self._store = {}
        self._base_path = Path(base_path)

    def __getitem__(self, key):
        if key in self._store:
            return self._store[key]

        p = (self._base_path/key).with_suffix(".yml")
        
        with open(p) as f:
            raw_data = f.read()

        data = load_yaml(raw_data)

        self._store[key] = data

        return data