Skip to main content

lib/crosswake/shell/fixtures.ex

defmodule Crosswake.Shell.Fixtures do
  @moduledoc """
  Canonical bundled shell fixtures derived from manifest truth, shared activation
  contracts, and stable denial vocabulary.
  """

  alias Crosswake.Manifest
  alias Crosswake.Manifest.Types
  alias Crosswake.Shell.Activation
  alias Crosswake.Shell.Denial
  alias Crosswake.SupportMatrix

  @declared_pack_requirements %{"shell.chrome" => "1.0.0"}
  @installed_packs %{"shell.chrome" => "1.0.0"}
  @pack_inventory [
    %{
      "pack_id" => "shell.chrome",
      "required_version" => "1.0.0",
      "installed_version" => "1.0.0",
      "bytes" => 24576,
      "integrity_status" => "verified",
      "verified_at" => "2026-05-17T09:00:00Z",
      "status" => "available"
    }
  ]
  @capabilities %{"app.info.get" => "1.0.0"}

  @spec export(String.t()) :: %{optional(String.t()) => String.t()}
  def export(platform) when platform in ["ios", "android"] do
    fixture_root = fixture_root(platform)

    %{
      Path.join(fixture_root, "crosswake_manifest.json") => manifest_json(),
      Path.join(fixture_root, "route_activation.json") => activation_json(platform),
      Path.join(fixture_root, "route_denial.json") => denial_json(),
      Path.join(fixture_root, "declared_pack_requirements.json") =>
        json!(@declared_pack_requirements),
      Path.join(fixture_root, "installed_packs.json") => json!(@installed_packs),
      Path.join(fixture_root, "pack_inventory.json") => json!(@pack_inventory)
    }
  end

  @spec manifest() :: Types.Root.t()
  def manifest do
    Types.new_root(
      crosswake_version: Mix.Project.config()[:version] || "dev",
      generated_at: "2026-05-15T00:00:00Z",
      host: Types.new_host(),
      compatibility: Types.new_compatibility(),
      support_matrix: SupportMatrix.canonical(),
      capability_registry: %{
        "app.info.get" => Types.new_capability(id: "app.info.get", version: "1.0.0")
      },
      routes: %{
        "dashboard" =>
          Types.new_route_entry(
            id: "dashboard",
            path: "/dashboard",
            runtime: :live_view,
            offline: :unavailable,
            capabilities: ["app.info.get"],
            packs: ["shell.chrome@1.0.0"],
            allowlisted_origins: [Types.default_origin()]
          )
      }
    )
  end

  defp activation_json(platform) do
    platform
    |> activation_request()
    |> Activation.to_map()
    |> json!()
  end

  defp activation_request(platform) do
    Activation.new_request(
      route_id: "dashboard",
      url: "#{Types.default_origin()}/dashboard",
      source: if(platform == "android", do: :deep_link, else: :cold_start),
      manifest_source: :bundled,
      bridge_protocol_version: "1.0.0",
      native_runtime_version: "1.0.0",
      correlation_id: "#{platform}-bootstrap-1",
      declared_pack_requirements: @declared_pack_requirements,
      installed_packs: @installed_packs,
      capabilities: @capabilities
    )
  end

  defp denial_json do
    Denial.new(
      reason: :pack_incompatible,
      route_id: "dashboard",
      message: "The shell is missing a compatible declared pack for this route.",
      hint: "ship the required compatible shell pack before activating the route",
      details: %{
        required_pack: "shell.chrome@1.0.0",
        installed_packs: @installed_packs
      },
      recovery: %{
        actions: ["retry", "update_app"],
        mode: "fail_closed"
      }
    )
    |> Denial.to_map()
    |> json!()
  end

  defp manifest_json do
    manifest()
    |> Manifest.render()
  end

  defp fixture_root("ios"), do: "Fixtures"
  defp fixture_root("android"), do: "assets"

  defp json!(value),
    do: Jason.encode_to_iodata!(value, pretty: true) |> IO.iodata_to_binary() |> Kernel.<>("\n")
end