import collections
import dataclasses
import difflib
import fileinput
import glob
import io
import os.path
import sys
from contextlib import nullcontext
from typing import Iterable, Iterator
from . import formatter, options_lib
from autobean_refactor import models, parser as parser_lib


@dataclasses.dataclass(frozen=True)
class _File:
    filename: str
    text: str
    model: models.File


def _get_include_paths(path: str, file: models.File) -> Iterable[str]:
    for directive in file.raw_directives:
        if not isinstance(directive, models.Include):
            continue
        matches = glob.glob(os.path.join(os.path.dirname(path), directive.filename), recursive=True)
        if not matches:
            lineno = directive.token_store.get_position(directive.first_token).line
            raise ValueError(f'No files match {directive.filename!r} ({path}:{lineno})')
        yield from matches


class _FilesFormatter:
    def __init__(self, options: options_lib.Options) -> None:
        self._parser = parser_lib.Parser()
        self._options = options

    def load_files(self, filename: str) -> Iterator[_File]:
        visited = set()
        queue = collections.deque([filename])

        while queue:
            filename = queue.popleft()
            if filename in visited:
                continue
            visited.add(filename)
            with fileinput.input(files=filename, encoding='utf-8') as f:
                text = ''.join(f)
            model = self._parser.parse(text, models.File)
            model.auto_claim_comments()
            yield _File(filename=filename, text=text, model=model)
            if self._options.recursive:
                queue.extend(_get_include_paths(filename, model))

    def format_file(self, file: _File) -> str:
        stream = io.StringIO()
        formatter.format(file.model, self._parser, self._options, stream)
        return stream.getvalue()

    def output_file(self, file: _File, formatted: str) -> None:
        match self._options.output_mode:
            case options_lib.OutputMode.STDOUT:
                sys.stdout.write(formatted)
                sys.stdout.flush()
            case options_lib.OutputMode.DIFF:
                diff = difflib.unified_diff(
                    file.text.splitlines(keepends=True),
                    formatted.splitlines(keepends=True),
                    fromfile=file.filename,
                    tofile=file.filename + ' (formatted)')
                sys.stdout.writelines(diff)
                sys.stdout.flush()
            case options_lib.OutputMode.INPLACE:
                with open(file.filename, 'w', encoding='utf-8') as f:
                    f.write(formatted)


def main() -> None:
    filename, options = options_lib.parse_args()

    formatter = _FilesFormatter(options)

    for file in formatter.load_files(filename):
        formatted = formatter.format_file(file)
        formatter.output_file(file, formatted)

