import { BestAgentError } from "@best-agent/foundation";
import type {
  AgentDefinition,
  AgentDefinitionModelStrategy,
  AgentLoopKind,
  AgentProfileSource,
  AgentProfileSummary,
  PermissionRule,
} from "@best-agent/protocol";

export interface AgentProfile {
  allowedTools: string[];
  customLoopId?: string;
  defaultSkills?: string[];
  description: string;
  loopKind?: AgentLoopKind;
  maxChildTasks?: number;
  maxProviderTurns?: number;
  maxToolSteps?: number;
  modelStrategy?: AgentDefinitionModelStrategy;
  name: string;
  permissions?: PermissionRule[];
  source: AgentProfileSource;
  sourcePlugin?: string;
  systemPrompt?: string;
}

export type AgentProfileInput = AgentProfile | AgentDefinition;

interface AgentProfileRegistryOptions {
  availableToolNames?: string[];
}

interface CreateAgentProfileRegistryOptions extends AgentProfileRegistryOptions {
  profiles?: AgentProfileInput[];
}

export class AgentProfileRegistry {
  readonly #profiles = new Map<string, AgentProfile>();

  constructor(profiles: AgentProfileInput[], options: AgentProfileRegistryOptions = {}) {
    const availableToolNames = deriveAvailableToolNames(profiles, options.availableToolNames);

    for (const profile of [...BUILT_IN_PROFILES, ...profiles].map((input) =>
      normalizeAgentProfile(input, availableToolNames),
    )) {
      this.#profiles.set(profile.name, profile);
    }
  }

  get(name: string): AgentProfile | undefined {
    return this.#profiles.get(name);
  }

  require(name: string): AgentProfile {
    const profile = this.#profiles.get(name);
    if (!profile) {
      throw new BestAgentError("AGENT_PROFILE_NOT_FOUND", `Agent profile ${name} was not found.`);
    }
    return profile;
  }

  list(): AgentProfileSummary[] {
    return Array.from(this.#profiles.values()).map((profile) => ({
      allowedTools: profile.allowedTools,
      defaultSkills: profile.defaultSkills,
      description: profile.description,
      name: profile.name,
      source: profile.source,
    }));
  }

  resolve(input: { prompt: string; requestedProfile?: string; strictRequested?: boolean }): {
    profile: AgentProfile;
    prompt: string;
  } {
    const requested = input.requestedProfile?.trim();
    if (requested) {
      const profile = this.#profiles.get(requested);
      if (profile) {
        return {
          profile,
          prompt: input.prompt,
        };
      }

      if (input.strictRequested) {
        throw new BestAgentError(
          "AGENT_PROFILE_NOT_FOUND",
          `Agent profile ${requested} was not found.`,
        );
      }
    }

    const routed = routePromptToProfile(input.prompt);
    if (routed) {
      const profile = this.#profiles.get(routed.profileName);
      if (profile) {
        return {
          profile,
          prompt: routed.prompt,
        };
      }
    }

    return {
      profile: this.require("orchestrator"),
      prompt: input.prompt,
    };
  }
}

export function createAgentProfileRegistry(
  profilesOrOptions: AgentProfileInput[] | CreateAgentProfileRegistryOptions = [],
  availableToolNames?: string[],
): AgentProfileRegistry {
  if (Array.isArray(profilesOrOptions)) {
    return new AgentProfileRegistry(profilesOrOptions, { availableToolNames });
  }

  return new AgentProfileRegistry(profilesOrOptions.profiles ?? [], {
    availableToolNames: profilesOrOptions.availableToolNames,
  });
}

const BUILT_IN_PROFILES: AgentProfile[] = [
  {
    name: "orchestrator",
    description:
      "Default general-purpose profile that can coordinate the full built-in runtime surface.",
    allowedTools: [
      "echo",
      "ask-user",
      "workspace-search",
      "workspace-files",
      "content-search",
      "read-file",
      "read-scratch",
      "lsp-goto-definition",
      "lsp-find-references",
      "lsp-diagnostics",
      "lsp-symbol-search",
      "shell",
      "process-session-start",
      "process-session-read",
      "process-session-write",
      "process-session-stop",
      "fetch-url",
      "web-search",
      "write-scratch",
      "append-scratch",
      "write-workspace",
      "verify-swift-package",
      "attach-text",
      "write-artifact",
      "delegate_tasks",
    ],
    defaultSkills: [],
    source: "built-in",
    systemPrompt:
      "You are the orchestrator profile. Prefer the simplest useful next action. Before creating a git commit, inspect git status and the current thread's changed files when available. If more than one non-empty scope could be committed, use ask-user to choose between current-thread changes, staged changes, unstaged changes, or all changes, and commit only the selected scope.",
  },
  {
    name: "explore",
    description: "Read-only exploration profile for code, files, and safe network lookups.",
    allowedTools: [
      "echo",
      "ask-user",
      "workspace-search",
      "workspace-files",
      "content-search",
      "read-file",
      "read-scratch",
      "lsp-goto-definition",
      "lsp-find-references",
      "lsp-diagnostics",
      "lsp-symbol-search",
      "fetch-url",
      "web-search",
    ],
    defaultSkills: [],
    source: "built-in",
    systemPrompt:
      "You are the explore profile. Stay read-only and focus on gathering high-signal context.",
  },
  {
    name: "plan",
    description:
      "Planning profile for architecture, sequencing, and tradeoff analysis without writes.",
    allowedTools: [
      "echo",
      "ask-user",
      "workspace-search",
      "workspace-files",
      "content-search",
      "read-file",
      "read-scratch",
      "lsp-goto-definition",
      "lsp-find-references",
      "lsp-diagnostics",
      "lsp-symbol-search",
      "fetch-url",
      "web-search",
    ],
    defaultSkills: [],
    source: "built-in",
    systemPrompt:
      "You are the plan profile. Produce clear implementation plans and avoid making changes.",
  },
  {
    name: "build",
    description: "Build profile for implementation work across the built-in tool surface.",
    allowedTools: [
      "echo",
      "ask-user",
      "workspace-search",
      "workspace-files",
      "content-search",
      "read-file",
      "read-scratch",
      "lsp-goto-definition",
      "lsp-find-references",
      "lsp-diagnostics",
      "lsp-symbol-search",
      "shell",
      "process-session-start",
      "process-session-read",
      "process-session-write",
      "process-session-stop",
      "fetch-url",
      "web-search",
      "write-scratch",
      "append-scratch",
      "write-workspace",
      "verify-swift-package",
      "attach-text",
      "write-artifact",
      "delegate_tasks",
    ],
    defaultSkills: [],
    maxProviderTurns: 24,
    source: "built-in",
    systemPrompt:
      "You are the build profile. Implement the requested change directly. When the user asks to run an existing command, run it after identifying the repo-defined entrypoint and arguments; do not keep inspecting optional docs or historical output directories. When that entrypoint has a concurrency or worker-count argument, satisfy a requested count by passing that argument once; start multiple independent processes only when the user explicitly asks for separate processes or jobs. Before final, run the user's requested smoke checks or artifact-producing commands yourself when tools permit; do not finish by telling the user to run them. Before creating a git commit, inspect git status and the current thread's changed files when available. If more than one non-empty scope could be committed, use ask-user to choose between current-thread changes, staged changes, unstaged changes, or all changes, and commit only the selected scope. Keep the output concise.",
  },
  {
    name: "verify",
    description:
      "Verification profile for checking behavior, running inspections, and validating changes.",
    allowedTools: [
      "echo",
      "ask-user",
      "workspace-search",
      "workspace-files",
      "content-search",
      "read-file",
      "read-scratch",
      "lsp-goto-definition",
      "lsp-find-references",
      "lsp-diagnostics",
      "lsp-symbol-search",
      "shell",
      "process-session-start",
      "process-session-read",
      "process-session-write",
      "process-session-stop",
      "fetch-url",
      "web-search",
      "verify-swift-package",
    ],
    defaultSkills: [],
    source: "built-in",
    systemPrompt:
      "You are the verify profile. Focus on evidence, validation, and reproducible checks.",
  },
  {
    name: "compact",
    description: "Compaction profile for summarizing thread state and preserving continuity.",
    allowedTools: [
      "echo",
      "ask-user",
      "workspace-search",
      "workspace-files",
      "content-search",
      "read-file",
      "read-scratch",
    ],
    defaultSkills: [],
    source: "built-in",
    systemPrompt:
      "You are the compact profile. Produce tight summaries that preserve only the most useful context.",
  },
];

function normalizeAgentProfile(
  input: AgentProfileInput,
  availableToolNames: string[],
): AgentProfile {
  if (isResolvedAgentProfile(input)) {
    return {
      ...input,
      allowedTools: unique(input.allowedTools),
      defaultSkills: input.defaultSkills ? unique(input.defaultSkills) : input.defaultSkills,
    };
  }

  return {
    allowedTools: resolveDefinitionTools(input, availableToolNames),
    customLoopId: input.customLoopId,
    defaultSkills: input.defaultSkills ? unique(input.defaultSkills) : input.defaultSkills,
    description: input.description,
    loopKind: input.loopKind,
    maxChildTasks: input.maxChildTasks,
    maxProviderTurns: input.maxProviderTurns,
    maxToolSteps: input.maxToolSteps,
    modelStrategy: input.modelStrategy,
    name: input.name,
    permissions: input.permissions ? [...input.permissions] : undefined,
    source: input.source,
    systemPrompt: input.systemPrompt,
  };
}

function deriveAvailableToolNames(profiles: AgentProfileInput[], explicit?: string[]): string[] {
  if (explicit && explicit.length > 0) {
    return unique(explicit);
  }

  const toolNames = new Set(BUILT_IN_PROFILES.flatMap((profile) => profile.allowedTools));
  for (const profile of profiles) {
    if (isResolvedAgentProfile(profile)) {
      for (const toolName of profile.allowedTools) {
        toolNames.add(toolName);
      }
      continue;
    }

    for (const toolName of profile.tools ?? []) {
      if (toolName !== "*") {
        toolNames.add(toolName);
      }
    }
  }
  return Array.from(toolNames);
}

function resolveDefinitionTools(
  definition: AgentDefinition,
  availableToolNames: string[],
): string[] {
  const allowedTools =
    definition.tools === undefined || definition.tools.includes("*")
      ? availableToolNames
      : availableToolNames.filter((toolName) => definition.tools?.includes(toolName));

  if (!definition.disallowedTools || definition.disallowedTools.length === 0) {
    return unique(allowedTools);
  }

  const disallowed = new Set(definition.disallowedTools);
  return unique(allowedTools.filter((toolName) => !disallowed.has(toolName)));
}

function isResolvedAgentProfile(profile: AgentProfileInput): profile is AgentProfile {
  return "allowedTools" in profile;
}

function unique(values: string[]): string[] {
  return Array.from(new Set(values));
}

function routePromptToProfile(prompt: string): { profileName: string; prompt: string } | undefined {
  const trimmed = prompt.trim();
  const routes = ["explore", "plan", "build", "verify", "compact"];

  for (const profileName of routes) {
    const prefix = `${profileName}:`;
    if (!trimmed.startsWith(prefix)) {
      continue;
    }

    return {
      profileName,
      prompt: trimmed.slice(prefix.length).trim(),
    };
  }

  return undefined;
}
