#!/usr/bin/env python3
# Copyright: 2026, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

"""Output format as an injectable Renderer.

The document format is an axis decopy is expected to vary (DEP-5 today, perhaps
an SPDX document or a JSON summary later). This module gathers the pieces of a
rendered document behind a single Renderer so the orchestration in output.py can
depend on the abstraction rather than hard-coding DEP-5.

A Renderer produces three pieces; the orchestration owns the surrounding
plumbing (writing, blank-line separators, choosing which groups/licenses to
emit):

  * header(copyright_, options)   - the leading Format/Source paragraph
  * file_block(group, options)    - one Files paragraph for a group
  * license_block(key, options)   - one standalone License stanza

Dep5Renderer is the default implementation (DEP-5 copyright-format 1.0). Nothing
imports this module yet; output.py is wired to it in a later step.
"""

from __future__ import annotations

import typing as t

from .core_types import License
from .spdx import render_license_name

if t.TYPE_CHECKING:
    from .copyright_processor import Copyright
    from .core_types import Options
    from .group_contracts import GroupProtocol


@t.runtime_checkable
class Renderer(t.Protocol):
    """Protocol for rendering the pieces of a copyright document."""

    def header(self, copyright_: Copyright | None, options: Options) -> str:
        """Render the leading header paragraph."""
        ...

    def file_block(
        self, group: GroupProtocol, options: Options, file_patterns: list[str]
    ) -> str:
        """Render the Files paragraph for a group.

        `file_patterns` is the already-resolved list of file names/wildcards for
        the Files: field; the caller owns how it was computed (wildcarding,
        cross-group tallying) so the renderer holds no shared state.
        """
        ...

    def license_block(self, key: str, options: Options) -> str:
        """Render the standalone License stanza for a license key."""
        ...


class Dep5Renderer(Renderer):
    """Renders DEP-5 copyright-format 1.0."""

    format_url = "https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/"

    def header(self, copyright_: Copyright | None, options: Options) -> str:
        if options.mode == "partial":
            return "Format: {}\nSource: TODO\nComment: *** only: {} ***".format(
                self.format_url,
                ", ".join(options.files),
            )
        if copyright_:
            header = copyright_.header
            header.format = self.format_url  # type: ignore[assignment]  # ty: ignore[invalid-assignment]
            if not header.source:
                header.source = "TODO"
            return (header.dump() or "").rstrip("\n")
        return f"Format: {self.format_url}\nSource: TODO"

    def file_block(
        self, group: GroupProtocol, options: Options, file_patterns: list[str]
    ) -> str:
        if not group.copyright_block_valid():
            return ""

        files = "\n       ".join(file_patterns)
        block = [f"Files: {files}"]
        if group.copyrights:
            holders = "\n           ".join(group.copyrights.sorted_members())
            block.append(f"Copyright: {holders}")
        block.append(f"License: {render_license_name(group.license, options)}")
        comments = group.get_comments()
        if comments:
            block.append(f"Comment: {comments}")
        # And finally add all the "unknown" fields
        if group.stored:
            for field in group.stored:  # type: ignore[attr-defined]  # ty: ignore[not-iterable]
                if field in {"Files", "Copyright", "License", "Comment"}:
                    continue
                block.append(f"{field}: {group.stored[field]}")
        return "\n".join(block)

    def license_block(self, key: str, options: Options) -> str:
        license_ = License.get(key)
        display = render_license_name(key, options)
        return self._license_stanza(license_, display)

    @staticmethod
    def _license_stanza(license_: License, display_name: str | None = None) -> str:
        """Render the DEP-5 License stanza, optionally overriding the name."""
        display = display_name if display_name is not None else license_.name
        if license_.stored and (dump := license_.stored.dump()) is not None:
            body = dump.rstrip("\n")
            newline_pos = body.find("\n")
            if newline_pos >= 0:
                return f"License: {display}{body[newline_pos:]}"
            return f"License: {display}"
        return f"License: {display}\nComment: Add the corresponding license text here"


_DEP5_RENDERER = Dep5Renderer()


def renderer_for(options: Options) -> Renderer:
    """Select the output renderer for the run.

    Only DEP-5 copyright-format 1.0 is supported today; this is the single seam
    where a future --output-format option would choose a different Renderer.
    """
    _ = options
    return _DEP5_RENDERER
