lib/foundry/copilot/context_builder.ex

defmodule Foundry.Copilot.ContextBuilder do
  def build(opts \\ []) do
    project_root = Keyword.get(opts, :project_root, File.cwd!())
    {:ok, manifest} = Foundry.Manifest.Parser.read(project_root)
    {nodes, edges} = Foundry.Context.GraphBuilder.build(project_root, manifest)
    spec_kit = Foundry.Context.SpecKitIndexBuilder.build(project_root)

    data = %{
      project_root: project_root,
      manifest: manifest,
      nodes: nodes,
      edges: edges,
      spec_kit: spec_kit
    }

    [tier_0_core(), tier_1_project(data), tier_2_status(data)]
    |> Enum.reject(&(&1 == ""))
    |> Enum.join("\n\n---\n\n")
  end

  # Tier 0: Foundry methodology — ships with the app, never changes per-project
  defp tier_0_core do
    Application.app_dir(:foundry, "priv/prompts/core.md") |> File.read!()
  end

  # Tier 1: Project identity — AGENTS.md + stack versions from mix.exs
  defp tier_1_project(data) do
    agents_md = read_file(Path.join(data.project_root, "AGENTS.md"))
    mix_versions = extract_mix_versions(data.project_root)

    [agents_md, mix_versions]
    |> Enum.reject(&(&1 == ""))
    |> Enum.join("\n\n")
  end

  # Tier 2: Dynamic status + LLM-optimized full project map for the orchestrator.
  defp tier_2_status(data) do
    status = get_project_status(data)
    context_map = get_system_context(data)
    [status, context_map] |> Enum.reject(&(&1 == "")) |> Enum.join("\n\n")
  end

  defp get_project_status(data) do
    status = Foundry.Status.build_from_nodes(data.project_root, data.manifest, data.nodes)
    "## Project Status\n\n```json\n#{Jason.encode!(status_prompt_view(status))}\n```"
  end

  defp get_system_context(data) do
    context =
      Foundry.Context.ProjectMap.assemble(data.manifest, data.nodes, data.edges, data.spec_kit)

    formatted = Foundry.Context.LLMFormatter.format(context)
    "## System Architecture (Full Project Context)\n\n#{formatted}"
  rescue
    e ->
      IO.warn("⚠️  System Architecture context error: #{inspect(e)}")
      ""
  end

  defp extract_mix_versions(project_root) do
    mix_path = Path.join(project_root, "mix.exs")

    case File.read(mix_path) do
      {:ok, contents} ->
        # Regex to find deps list in Elixir
        case Regex.run(~r/defp deps\s*(?:do|\(.*\)\s*do)\s*(.*?)\n\s*end/s, contents) do
          [_, deps] -> "## Dependency Versions\n\n```elixir\n#{deps}\n```"
          _ -> ""
        end

      _ ->
        ""
    end
  end

  defp status_prompt_view(status) do
    compliance = status["compliance"] || %{}
    requirements = compliance["requirements"] || []

    %{
      "generated_at" => status["generated_at"],
      "compiled_at" => status["compiled_at"],
      "project" => status["project"],
      "project_type" => status["project_type"],
      "domain_type" => status["domain_type"],
      "domains" => status["domains"],
      "sensitive_modules" => status["sensitive_modules"],
      "lint" => status["lint"],
      "migrations" => status["migrations"],
      "proposals" => status["proposals"],
      "compliance" => %{
        "total_requirements" => compliance["total_requirements"],
        "covered_count" => compliance["covered_count"],
        "planned_count" => compliance["planned_count"],
        "sample_requirements" => Enum.take(requirements, 5),
        "truncated_count" => max(length(requirements) - 5, 0)
      },
      "ci" => status["ci"],
      "stack" => status["stack"],
      "manifest" => status["manifest"]
    }
  end

  defp read_file(path) do
    case File.read(path) do
      {:ok, contents} -> contents
      _ -> ""
    end
  end
end