from __future__ import annotations
# Allow direct execution
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import enum
import itertools
import json
import logging
import re
from collections import defaultdict
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from devscripts.utils import read_file, run_process, write_file
BASE_URL = 'https://github.com'
LOCATION_PATH = Path(__file__).parent
HASH_LENGTH = 7
logger = logging.getLogger(__name__)
class CommitGroup(enum.Enum):
    PRIORITY = 'Important'
    CORE = 'Core'
    EXTRACTOR = 'Extractor'
    DOWNLOADER = 'Downloader'
    POSTPROCESSOR = 'Postprocessor'
    NETWORKING = 'Networking'
    MISC = 'Misc.'
    @classmethod
    @lru_cache
    def subgroup_lookup(cls):
        return {
            name: group
            for group, names in {
                cls.MISC: {
                    'build',
                    'ci',
                    'cleanup',
                    'devscripts',
                    'docs',
                    'test',
                },
                cls.NETWORKING: {
                    'rh',
                },
            }.items()
            for name in names
        }
    @classmethod
    @lru_cache
    def group_lookup(cls):
        result = {
            'fd': cls.DOWNLOADER,
            'ie': cls.EXTRACTOR,
            'pp': cls.POSTPROCESSOR,
            'upstream': cls.CORE,
        }
        result.update({item.name.lower(): item for item in iter(cls)})
        return result
    @classmethod
    def get(cls, value: str) -> tuple[CommitGroup | None, str | None]:
        group, _, subgroup = (group.strip().lower() for group in value.partition('/'))
        if result := cls.group_lookup().get(group):
            return result, subgroup or None
        if subgroup:
            return None, value
        return cls.subgroup_lookup().get(group), group or None
@dataclass
class Commit:
    hash: str | None
    short: str
    authors: list[str]
    def __str__(self):
        result = f'{self.short!r}'
        if self.hash:
            result += f' ({self.hash[:HASH_LENGTH]})'
        if self.authors:
            authors = ', '.join(self.authors)
            result += f' by {authors}'
        return result
@dataclass
class CommitInfo:
    details: str | None
    sub_details: tuple[str, ...]
    message: str
    issues: list[str]
    commit: Commit
    fixes: list[Commit]
    def key(self):
        return ((self.details or '').lower(), self.sub_details, self.message)
def unique(items):
    return sorted({item.strip().lower(): item for item in items if item}.values())
class Changelog:
    MISC_RE = re.compile(r'(?:^|\b)(?:lint(?:ing)?|misc|format(?:ting)?|fixes)(?:\b|$)', re.IGNORECASE)
    ALWAYS_SHOWN = (CommitGroup.PRIORITY,)
    def __init__(self, groups, repo, collapsible=False):
        self._groups = groups
        self._repo = repo
        self._collapsible = collapsible
    def __str__(self):
        return '\n'.join(self._format_groups(self._groups)).replace('\t', '    ')
    def _format_groups(self, groups):
        first = True
        for item in CommitGroup:
            if self._collapsible and item not in self.ALWAYS_SHOWN and first:
                first = False
                yield '\nChangelog
\n'
            if group := groups[item]:
                yield self.format_module(item.value, group)
        if self._collapsible:
            yield '\n