# SPDX-License-Identifier: Apache-2.0
# Copyright 2024 The Meson development team

from __future__ import annotations
from collections import defaultdict
import os
import tempfile
import typing as T
import subprocess

from .run_tool import run_tool_on_targets, run_with_buffered_output
from .. import build, mlog
from ..mesonlib import MachineChoice, PerMachine
from ..tooldetect import detect_ninja

if T.TYPE_CHECKING:
    from ..compilers.rust import RustCompiler

class ClippyDriver:
    def __init__(self, build: build.Build, tempdir: str, args: list[str]):
        self.tools: PerMachine[T.List[str]] = PerMachine([], [])
        self.warned: T.DefaultDict[str, bool] = defaultdict(lambda: False)
        self.tempdir = tempdir
        for machine in MachineChoice:
            compilers = build.environment.coredata.compilers[machine]
            if 'rust' in compilers:
                compiler = T.cast('RustCompiler', compilers['rust'])
                self.tools[machine] = compiler.get_rust_tool('clippy-driver')
        self.args = args

    def warn_missing_clippy(self, machine: str) -> None:
        if self.warned[machine]:
            return
        mlog.warning(f'clippy-driver not found for {machine} machine')
        self.warned[machine] = True

    def __call__(self, target: T.Dict[str, T.Any]) -> T.Iterable[T.Coroutine[None, None, int]]:
        for src_block in target['target_sources']:
            if 'compiler' in src_block and src_block['language'] == 'rust':
                clippy = getattr(self.tools, src_block['machine'])
                if not clippy:
                    self.warn_missing_clippy(src_block['machine'])
                    continue

                cmdlist = list(clippy)
                prev = None
                lints_cap = None
                for arg in src_block['parameters']:
                    if prev == '--cap-lints':
                        cmdlist.append(prev)
                        lints_cap = arg
                        prev = None
                    elif prev:
                        prev = None
                        continue
                    if arg in {'--emit', '--out-dir', '--cap-lints'}:
                        prev = arg
                    else:
                        cmdlist.append(arg)

                # no use in running clippy if it wouldn't print anything anyway
                if lints_cap == 'allow':
                    break

                cmdlist.extend(src_block['sources'])
                # the default for --emit is to go all the way to linking,
                # and --emit dep-info= is not enough for clippy to do
                # enough analysis, so use --emit metadata.
                cmdlist.append('--emit')
                cmdlist.append('metadata')
                cmdlist.append('--out-dir')
                cmdlist.append(self.tempdir)
                cmdlist += self.args
                yield run_with_buffered_output(cmdlist)

def run(args: T.List[str]) -> int:
    os.chdir(args[0])
    build_data = build.load(os.getcwd())

    # Build as much of the project as possible, or else
    # we get errors about missing libraries in the build directory and
    # other related errors.
    subprocess.run(detect_ninja() + ['clippy-json-prereq', '-k0'])

    with tempfile.TemporaryDirectory() as d:
        return run_tool_on_targets(ClippyDriver(build_data, d, args[1:]))
