defmodule TarakanWeb.RepositoryComponents do
  @moduledoc """
  Shared repository page chrome.

  Every repository-scoped page renders the same header at the same scale:
  status badge row, display-face title, canonical source link, host metadata,
  and the Code / Commits / Security tab rail. Page bodies differ; the frame does not.
  """

  use Phoenix.Component

  import TarakanWeb.CoreComponents,
    only: [icon: 1, breadcrumbs: 1, handle_link: 1, notch_badge: 1]

  alias TarakanWeb.RepositoryPaths

  @doc ~S'Formats a star count compactly (`1200` -> `"1.2k"`); `nil` -> `"0"`.'
  def compact_stars(count) when is_integer(count) and count >= 1000,
    do: "#{Float.round(count / 1000, 1)}k"

  def compact_stars(count) when is_integer(count), do: Integer.to_string(count)
  def compact_stars(_count), do: "0"

  attr :repository, Tarakan.Repositories.Repository, required: true
  attr :active_tab, :atom, required: true, values: [:code, :commits, :security]

  def repository_header(assigns) do
    ~H"""
    <header>
      <.breadcrumbs id="repository-breadcrumb">
        <:crumb navigate="/">registry</:crumb>
        <:crumb navigate={RepositoryPaths.repository_path(@repository)}>
          {@repository.owner}
        </:crumb>
        <:crumb navigate={RepositoryPaths.repository_path(@repository)}>
          {@repository.name}
        </:crumb>
      </.breadcrumbs>

      <div class="flex flex-wrap items-center gap-3">
        <.notch_badge
          id="repository-status"
          class={status_badge_class(@repository)}
          title={repository_status_title(@repository)}
        >
          {repository_status_label(@repository)}
        </.notch_badge>
        <.notch_badge :if={@repository.archived} class="text-signal">
          archived
        </.notch_badge>
        <.notch_badge
          :if={@repository.managed_disclosure}
          id="repository-managed-disclosure"
          class="text-quote"
          title="Disclosure to the vendor is handled by Tarakan staff. Handling only - the public record is unchanged."
        >
          Managed disclosure
        </.notch_badge>
      </div>

      <div class="mt-3 flex flex-col justify-between gap-4 lg:flex-row lg:items-end">
        <div class="min-w-0">
          <h1
            id="repository-name"
            class="break-words font-display text-2xl font-medium uppercase leading-tight tracking-[0.02em] text-ink sm:text-3xl"
          >
            <span class="text-ink-faint">{@repository.owner}/</span>{@repository.name}
          </h1>
          <p :if={@repository.description} class="mt-2 max-w-3xl text-sm leading-6 text-ink-muted">
            {@repository.description}
          </p>
        </div>
        <a
          :if={not Tarakan.Repositories.Repository.hosted?(@repository)}
          id="repository-source-link"
          href={@repository.canonical_url}
          rel="noreferrer"
          target="_blank"
          class="inline-flex max-w-full min-w-0 items-center gap-2 font-mono text-xs text-ink-muted transition hover:text-signal"
        >
          <span class="min-w-0 break-all">{@repository.canonical_url}</span>
          <.icon name="hero-arrow-up-right" class="size-4 shrink-0" />
        </a>
      </div>

      <div
        id="github-metadata"
        class="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2 font-mono text-[11px] text-ink-faint sm:gap-x-5 sm:text-xs"
      >
        <span :if={@repository.primary_language} class="inline-flex items-center gap-2">
          <span class="size-2 bg-signal"></span>
          {@repository.primary_language}
        </span>
        <span
          :if={not Tarakan.Repositories.Repository.hosted?(@repository)}
          class="inline-flex items-center gap-1.5"
        >
          <.icon name="hero-star" class="size-3.5" /> {@repository.stars_count} stars
        </span>
        <span
          :if={not Tarakan.Repositories.Repository.hosted?(@repository)}
          class="inline-flex items-center gap-1.5"
        >
          <.icon name="hero-code-bracket" class="size-3.5" /> {@repository.forks_count} forks
        </span>
        <span :if={Tarakan.Repositories.Repository.hosted?(@repository)}>hosted on tarakan</span>
        <span :if={@repository.default_branch}>default: {@repository.default_branch}</span>
        <span class="inline-flex items-center gap-1.5">
          <.icon name="hero-clock" class="size-3.5" />
          registered {Calendar.strftime(
            @repository.inserted_at,
            "%Y-%m-%d"
          )}
        </span>
        <span :if={@repository.submitted_by} id="repository-submitter">
          by <.handle_link handle={@repository.submitted_by.handle} class="text-ink-muted" />
        </span>
      </div>

      <nav
        id="repository-navigation"
        class="mt-5 flex items-end gap-0 overflow-x-auto overscroll-x-contain border-b border-rule text-sm [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
        aria-label="Repository"
      >
        <.repository_tab
          id="repository-code-tab"
          active={@active_tab == :code}
          navigate={RepositoryPaths.repository_path(@repository)}
          icon="hero-code-bracket"
          label="Code"
        />
        <.repository_tab
          id="repository-commits-tab"
          active={@active_tab == :commits}
          navigate={RepositoryPaths.repository_commits_path(@repository)}
          icon="hero-clock"
          label="Commits"
        />
        <.repository_tab
          id="repository-overview-tab"
          active={@active_tab == :security}
          navigate={RepositoryPaths.repository_security_path(@repository)}
          icon="hero-shield-check"
          label="Security"
        >
          <span
            :if={security_tab_count(@repository)}
            class={[
              "rounded-full px-1.5 py-0.5 font-mono text-[10px] leading-none",
              security_tab_count_class(@repository)
            ]}
          >
            {security_tab_count(@repository)}
          </span>
        </.repository_tab>
      </nav>
    </header>
    """
  end

  attr :id, :string, required: true
  attr :active, :boolean, required: true
  attr :navigate, :string, required: true
  attr :icon, :string, required: true
  attr :label, :string, required: true
  slot :inner_block

  # The active tab stays a link. It used to render as a span, which made the
  # most natural navigation on the page impossible: from a commit detail page
  # the Commits tab points at the commit list, and from a nested file the Code
  # tab points at the repository root, and neither could be clicked. A span is
  # also not focusable, so keyboard users tabbed straight past the section they
  # were in. aria-current carries the "you are here" meaning instead.
  defp repository_tab(assigns) do
    ~H"""
    <.link
      id={@id}
      navigate={@navigate}
      aria-current={@active && "page"}
      class={[
        "-mb-px inline-flex shrink-0 items-center gap-2 border-b-2 px-3 py-3 transition sm:px-4",
        @active && "border-signal font-semibold text-ink",
        !@active && "border-transparent text-ink-muted hover:border-rule hover:text-ink"
      ]}
    >
      <.icon name={@icon} class="size-4" /> {@label} {render_slot(@inner_block)}
    </.link>
    """
  end

  @doc """
  The scan's submitted Tarakan Scan Format document, pretty-printed for the
  raw-report block. Returns the stored text unchanged when it isn't JSON.
  """
  def raw_report(%{raw_document: nil}), do: nil

  def raw_report(%{raw_document: raw_document}) do
    Jason.Formatter.pretty_print(raw_document)
  rescue
    _not_json -> raw_document
  end

  @doc """
  Short label for who may do a job, or how a submission was made.

  Hybrid is agent draft with a person in the loop - not a description of
  Tarakan overall (the product is already human-owned).
  """
  def provenance_label("agent"), do: "Agent"
  def provenance_label("human"), do: "Human"
  def provenance_label("hybrid"), do: "Agent + human"
  def provenance_label(other), do: other

  @doc "Human-readable label for a review or task kind."
  def review_kind_label("code_review"), do: "Code review"
  def review_kind_label("threat_model"), do: "Threat model"
  def review_kind_label("privacy_review"), do: "Privacy review"
  def review_kind_label("business_logic"), do: "Business logic"
  def review_kind_label("verify_findings"), do: "Verify findings"
  def review_kind_label("write_fix"), do: "Write a fix"
  def review_kind_label("diff_review"), do: "Diff review"
  def review_kind_label("refute_finding"), do: "Refute a finding"
  def review_kind_label("reproduce_finding"), do: "Reproduce a finding"
  def review_kind_label("calibrate_severity"), do: "Re-score severity"
  def review_kind_label("synthesize_rule"), do: "Write a detector"
  def review_kind_label(other), do: other

  @doc """
  Short status for repo headers and meta.

  Avoids the useless lone label \"findings\" once every repo has some.
  Prefers open/verified counts and clear/unreviewed states.
  """
  def repository_status_label(%{status: "unscanned"}), do: "No report"

  def repository_status_label(%{open_findings_count: open, verified_findings_count: verified})
      when is_integer(open) and open > 0 do
    open_label = if open == 1, do: "1 open", else: "#{open} open"

    if is_integer(verified) and verified > 0 do
      verified_label = if verified == 1, do: "1 verified", else: "#{verified} verified"
      "#{open_label} · #{verified_label}"
    else
      open_label
    end
  end

  def repository_status_label(%{status: status}) when status in ["reviewed", "clear"],
    do: "Clear"

  def repository_status_label(%{scan_count: n}) when is_integer(n) and n > 0, do: "Clear"
  def repository_status_label(_repository), do: "No report"

  defp repository_status_title(%{open_findings_count: open, verified_findings_count: verified})
       when is_integer(open) and open > 0 do
    open_part =
      if open == 1,
        do: "1 unique finding still open",
        else: "#{open} unique findings still open"

    if is_integer(verified) and verified > 0 do
      verified_part =
        if verified == 1,
          do: "1 independently verified",
          else: "#{verified} independently verified"

      "#{open_part}; #{verified_part}"
    else
      open_part
    end
  end

  defp repository_status_title(%{status: "unscanned"}),
    do: "No public review on the record yet"

  defp repository_status_title(%{status: status}) when status in ["reviewed", "clear"],
    do: "Reported with no open findings"

  defp repository_status_title(%{scan_count: n}) when is_integer(n) and n > 0,
    do: "Reported with no open findings"

  defp repository_status_title(_repository), do: "No public review on the record yet"

  defp status_badge_class(%{open_findings_count: open}) when is_integer(open) and open > 0,
    do: "text-signal"

  defp status_badge_class(%{status: "unscanned"}), do: "text-ink-faint"

  defp status_badge_class(%{status: status}) when status in ["reviewed", "clear"],
    do: "text-quote"

  defp status_badge_class(_repository), do: "text-ink-muted"

  # Tab pill: open count when > 0, else verified count, else nothing for unscanned.
  defp security_tab_count(%{open_findings_count: open}) when is_integer(open) and open > 0,
    do: open

  defp security_tab_count(%{verified_findings_count: verified})
       when is_integer(verified) and verified > 0,
       do: verified

  defp security_tab_count(%{scan_count: scans}) when is_integer(scans) and scans > 0, do: "ok"
  defp security_tab_count(_repository), do: nil

  defp security_tab_count_class(%{open_findings_count: open}) when is_integer(open) and open > 0,
    do: "bg-signal text-ground"

  defp security_tab_count_class(_repository), do: "bg-panel text-ink-muted border border-rule"
end
