from __future__ import annotations

from flow_xhs.cli.spec.enums import AliasVisibility, ConstraintSeverity, ParameterGroup, RequirednessMode
from flow_xhs.cli.spec.model import CliSpec, CommandSpec, ConstraintSpec, DocTopicSpec, ParameterSpec
from flow_xhs.cli.spec.views import GroupView, list_root_entries


GROUP_TITLES: dict[ParameterGroup, str] = {
    ParameterGroup.REQUIRED_INPUTS: "Required Inputs",
    ParameterGroup.CONDITIONAL_INPUTS: "Conditional Inputs",
    ParameterGroup.OPTIONAL_INPUTS: "Optional Inputs",
    ParameterGroup.INPUT_SOURCES: "Input Sources",
    ParameterGroup.EXECUTION_CONTROLS: "Execution Controls",
    ParameterGroup.OUTPUT_CONTROLS: "Output Controls",
    ParameterGroup.ADVANCED: "Advanced",
}


def render_root_help(spec: CliSpec, *, verbose: bool = False) -> str:
    lines: list[str] = [
        f"Usage: {spec.canonical_syntax}",
        "",
        spec.summary,
    ]
    if spec.description:
        lines.extend(["", spec.description])

    lines.extend(["", "Synopsis:", f"  {spec.canonical_syntax}"])

    lines.extend(["", "Commands:"])
    for entry in list_root_entries(spec):
        suffix = " (compat)" if entry.kind.startswith("alias") else ""
        lines.append(f"  {entry.name:<20} {entry.summary}{suffix}")

    if spec.global_options:
        lines.extend(["", "Global Options:"])
        for param in spec.global_options:
            lines.extend(_render_parameter_block(param, indent="  ", verbose=verbose))

    quick_start = tuple(spec.metadata.get("quick_start", ()))
    if quick_start:
        lines.extend(["", "Quick Start:"])
        for example in quick_start:
            lines.append(f"  {example}")

    if spec.topics:
        lines.extend(["", "Help Topics:"])
        for topic in spec.topics:
            lines.append(f"  {topic.id:<20} {topic.summary}")

    return "\n".join(lines)


def render_command_help(
    spec: CliSpec,
    command: CommandSpec,
    *,
    invoked_path: tuple[str, ...] | None = None,
    verbose: bool = False,
) -> str:
    help_spec = command.help or command_help_defaults()
    lines: list[str] = [
        f"Usage: {command.display_path} [OPTIONS]",
        "",
        command.summary,
    ]
    if command.description:
        lines.extend(["", command.description])

    compatibility_alias = _get_invoked_alias(command, invoked_path)
    if compatibility_alias is not None:
        suffix = ""
        if compatibility_alias.deprecated:
            suffix = " (deprecated"
            if compatibility_alias.remove_after:
                suffix += f"; remove_after={compatibility_alias.remove_after}"
            suffix += ")"
        lines.extend(
            [
                "",
                "Compatibility Alias:",
                f"  {compatibility_alias.display_path} -> {compatibility_alias.replacement or command.display_path}{suffix}",
            ]
        )

    if _should_show_aliases(help_spec, verbose):
        aliases = _visible_aliases(
            command,
            verbose=verbose,
            include_deprecated=help_spec.show_deprecated_aliases or verbose,
        )
        if aliases:
            lines.extend(["", "Aliases:"])
            for alias in aliases:
                replacement = f" -> {alias.replacement}" if alias.replacement else ""
                suffix = ""
                if alias.deprecated:
                    suffix = " (deprecated"
                    if alias.remove_after:
                        suffix += f"; remove_after={alias.remove_after}"
                    suffix += ")"
                lines.append(f"  {alias.display_path}{replacement}{suffix}")

    grouped_params = _group_parameters(spec, command)
    section_renderers = {
        "required_inputs": lambda: _render_grouped_params(grouped_params, ParameterGroup.REQUIRED_INPUTS, verbose=verbose),
        "conditional_inputs": lambda: _render_grouped_params(grouped_params, ParameterGroup.CONDITIONAL_INPUTS, verbose=verbose),
        "optional_inputs": lambda: _render_grouped_params(grouped_params, ParameterGroup.OPTIONAL_INPUTS, verbose=verbose),
        "input_sources": lambda: _render_grouped_params(grouped_params, ParameterGroup.INPUT_SOURCES, verbose=verbose),
        "execution_controls": lambda: _render_grouped_params(grouped_params, ParameterGroup.EXECUTION_CONTROLS, verbose=verbose),
        "output_controls": lambda: _render_grouped_params(grouped_params, ParameterGroup.OUTPUT_CONTROLS, verbose=verbose),
        "advanced": lambda: _render_grouped_params(grouped_params, ParameterGroup.ADVANCED, verbose=verbose),
        "constraints": lambda: _render_constraints(command.constraints),
        "examples": lambda: _render_examples(command),
        "related_commands": lambda: _render_related_commands(command),
    }

    for section in help_spec.section_order:
        if section in {"usage", "summary"}:
            continue
        if section not in help_spec.verbose_sections or verbose:
            rendered = section_renderers.get(section, lambda: [])()
            if rendered:
                lines.extend(["", *rendered])

    return "\n".join(lines)


def render_group_help(group: GroupView, *, verbose: bool = False) -> str:
    lines: list[str] = [
        f"Usage: {group.usage}",
        "",
        group.summary,
    ]
    if group.description:
        lines.extend(["", group.description])
    if group.deprecated:
        lines.extend(["", "Status:", "  deprecated compatibility entry"])

    visible_aliases = [alias for alias in group.aliases if _alias_visible(alias.visibility, verbose=verbose)]
    if visible_aliases:
        lines.extend(["", "Aliases:"])
        for alias in visible_aliases:
            replacement = f" -> {alias.replacement}" if alias.replacement else ""
            suffix = ""
            if alias.deprecated:
                suffix = " (deprecated"
                if alias.remove_after:
                    suffix += f"; remove_after={alias.remove_after}"
                suffix += ")"
            lines.append(f"  {alias.display_path}{replacement}{suffix}")

    if group.commands:
        lines.extend(["", "Commands:"])
        for entry in group.commands:
            line = f"  {entry.name:<20} {entry.summary}"
            if entry.replacement:
                line += f" -> {entry.replacement}"
            if entry.deprecated:
                line += " (compat)"
            lines.append(line)

    if group.global_options:
        lines.extend(["", "Global Options:"])
        for param in group.global_options:
            lines.extend(_render_parameter_block(param, indent="  ", verbose=verbose))

    return "\n".join(lines)


def render_topic_help(topic: DocTopicSpec) -> str:
    lines = [
        f"Topic: {topic.id}",
        "",
        topic.title,
        "",
        topic.summary,
    ]
    if topic.content_markdown:
        lines.extend(["", topic.content_markdown])
    return "\n".join(lines)


def _group_parameters(spec: CliSpec, command: CommandSpec) -> dict[ParameterGroup, tuple[ParameterSpec, ...]]:
    grouped: dict[ParameterGroup, tuple[ParameterSpec, ...]] = {}
    command_params = command.parameters
    output_controls = tuple(spec.global_options)
    all_groups = {}
    for param in command_params + output_controls:
        all_groups.setdefault(param.group, []).append(param)
    return {group: tuple(params) for group, params in all_groups.items()}


def _render_grouped_params(
    grouped_params: dict[ParameterGroup, tuple[ParameterSpec, ...]],
    group: ParameterGroup,
    *,
    verbose: bool,
) -> list[str]:
    params = grouped_params.get(group, ())
    if not params:
        return []
    lines = [f"{GROUP_TITLES[group]}:"]
    for param in params:
        lines.extend(_render_parameter_block(param, indent="  ", verbose=verbose))
    return lines


def _render_constraints(constraints: tuple[ConstraintSpec, ...]) -> list[str]:
    if not constraints:
        return []
    lines = ["Constraints:"]
    for constraint in constraints:
        prefix = "warning" if constraint.severity == ConstraintSeverity.WARNING else "error"
        lines.append(f"  - [{prefix}] {_constraint_text(constraint)}")
    return lines


def _render_examples(command: CommandSpec) -> list[str]:
    if not command.examples:
        return []
    lines = ["Examples:"]
    for example in command.examples:
        lines.append(f"  {example.command}")
    return lines


def _render_related_commands(command: CommandSpec) -> list[str]:
    if not command.related_commands:
        return []
    lines = ["Related Commands:"]
    for related in command.related_commands:
        lines.append(f"  {related}")
    return lines


def _render_parameter_block(param: ParameterSpec, *, indent: str, verbose: bool) -> list[str]:
    cli_names = ", ".join(param.cli_names)
    line = f"{indent}{cli_names}"
    if param.allowed_values:
        values = ", ".join(value.value for value in param.allowed_values if not value.hidden)
        line += f" [{values}]"
    if param.default and param.default.display_value:
        line += f" (default: {param.default.display_value})"
    line += f": {param.summary}"

    lines = [line]
    if verbose and param.description:
        lines.append(f"{indent}  details: {param.description}")
    if param.requiredness.mode == RequirednessMode.CONDITIONAL and param.requiredness.message:
        lines.append(f"{indent}  when: {param.requiredness.message}")
    if param.input_bindings:
        bindings = ", ".join(
            binding.field_name if binding.field_name else binding.source.value for binding in param.input_bindings
        )
        lines.append(f"{indent}  sources: {bindings}")
    if verbose:
        extra_meta = _render_value_type_details(param, indent=indent)
        lines.extend(extra_meta)
        enum_details = _render_allowed_value_details(param, indent=indent)
        lines.extend(enum_details)
    return lines


def _render_value_type_details(param: ParameterSpec, *, indent: str) -> list[str]:
    details: list[str] = []
    value_type = param.value_type
    range_parts: list[str] = []
    if value_type.min is not None:
        range_parts.append(f"min={value_type.min}")
    if value_type.max is not None:
        range_parts.append(f"max={value_type.max}")
    if value_type.min_length is not None:
        range_parts.append(f"min_length={value_type.min_length}")
    if value_type.max_length is not None:
        range_parts.append(f"max_length={value_type.max_length}")
    if range_parts:
        details.append(f"{indent}  range: {', '.join(range_parts)}")
    if value_type.format:
        details.append(f"{indent}  format: {value_type.format}")
    return details


def _render_allowed_value_details(param: ParameterSpec, *, indent: str) -> list[str]:
    if not param.allowed_values:
        return []
    lines: list[str] = []
    for allowed in param.allowed_values:
        if allowed.hidden:
            continue
        parts: list[str] = []
        if allowed.label:
            parts.append(f"label={allowed.label}")
        if allowed.aliases:
            parts.append(f"aliases={', '.join(allowed.aliases)}")
        if allowed.deprecated_aliases:
            parts.append(f"deprecated_aliases={', '.join(allowed.deprecated_aliases)}")
        if allowed.description:
            parts.append(allowed.description)
        if parts:
            lines.append(f"{indent}  value {allowed.value}: " + "; ".join(parts))
    return lines


def _constraint_text(constraint: ConstraintSpec) -> str:
    return constraint.help_text or constraint.message


def _visible_aliases(command: CommandSpec, *, verbose: bool, include_deprecated: bool) -> tuple:
    return tuple(
        alias
        for alias in command.aliases
        if _alias_visible(alias.visibility, verbose=verbose)
        and (include_deprecated or not alias.deprecated)
    )


def _alias_visible(visibility: AliasVisibility, *, verbose: bool) -> bool:
    if visibility == AliasVisibility.VISIBLE:
        return True
    if visibility == AliasVisibility.HIDDEN_UNLESS_VERBOSE:
        return verbose
    return False


def _get_invoked_alias(command: CommandSpec, path: tuple[str, ...] | None):
    if path is None or path == command.path:
        return None
    for alias in command.aliases:
        if alias.path == path:
            return alias
    return None


def _should_show_aliases(help_spec, verbose: bool) -> bool:
    return help_spec.show_aliases or verbose


def command_help_defaults():
    from flow_xhs.cli.spec.model import CommandHelpSpec

    return CommandHelpSpec()
