import { app } from "electron"
import path from "path"
import fs from "fs"
import {
  AgentProfile,
  AgentProfileRole,
  AgentProfilesData,
  AgentProfileToolConfig,
  ConversationMessage,
  Profile,
  ProfilesData,
  ProfileMcpServerConfig,
  ProfileModelConfig,
  ProfileSkillsConfig,
  SessionProfileSnapshot,
  Persona,
  PersonasData,
  MCPServerConfig,
  ACPAgentConfig,
  normalizeAgentProfileRole,
  profileToAgentProfile,
  personaToAgentProfile,
  acpAgentConfigToAgentProfile,
} from "@shared/types"
import { RESERVED_RUNTIME_TOOL_SERVER_NAMES } from "@shared/runtime-tool-names"
import { randomUUID } from "crypto"
import { logApp } from "./debug"
import { configStore, globalAgentsFolder, resolveWorkspaceAgentsFolder } from "./config"
import { getRuntimeToolNames } from "./runtime-tool-definitions"
import { getAgentsLayerPaths } from "./agents-files/modular-config"
import {
  loadAgentProfilesLayer,
  writeAgentsProfileFiles,
  writeAllAgentsProfileFiles,
  deleteAgentProfileFiles,
} from "./agents-files/agent-profiles"

/**
 * Path to the agent profiles storage file.
 */
export const agentProfilesPath = path.join(
  app.getPath("userData"),
  "agent-profiles.json"
)

/**
 * Path to the agent profile conversations storage file.
 */
export const agentProfileConversationsPath = path.join(
  app.getPath("userData"),
  "agent-profile-conversations.json"
)

// Legacy paths for migration
const legacyProfilesPath = path.join(app.getPath("userData"), "profiles.json")
const legacyPersonasPath = path.join(app.getPath("userData"), "personas.json")

// ============================================================================
// Validation Helpers (ported from the legacy profile service)
// ============================================================================

const RESERVED_SERVER_NAMES = [...RESERVED_RUNTIME_TOOL_SERVER_NAMES]
const VALID_PROVIDER_IDS = ["openai", "groq", "gemini", "chatgpt-web"]
const VALID_STT_PROVIDER_IDS = ["openai", "groq", "parakeet"]
const VALID_TTS_PROVIDER_IDS = ["openai", "groq", "gemini", "edge", "kitten", "supertonic"]

function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every((item) => typeof item === "string")
}

function isValidServerConfig(config: unknown): boolean {
  if (typeof config !== "object" || config === null || Array.isArray(config)) return false
  const c = config as Record<string, unknown>
  if (c.transport !== undefined && (typeof c.transport !== "string" || !["stdio", "websocket", "streamableHttp"].includes(c.transport))) return false
  if (c.command !== undefined && typeof c.command !== "string") return false
  if (c.args !== undefined && (!Array.isArray(c.args) || !c.args.every((arg) => typeof arg === "string"))) return false
  if (c.url !== undefined && typeof c.url !== "string") return false
  const transport = c.transport as string | undefined
  if (transport === "stdio" && !c.command) return false
  if ((transport === "websocket" || transport === "streamableHttp") && !c.url) return false
  if (transport === undefined && !c.command && !c.url) return false
  if (c.env !== undefined) {
    if (typeof c.env !== "object" || c.env === null || Array.isArray(c.env)) return false
    if (!Object.values(c.env as Record<string, unknown>).every((val) => typeof val === "string")) return false
  }
  if (c.headers !== undefined) {
    if (typeof c.headers !== "object" || c.headers === null || Array.isArray(c.headers)) return false
    if (!Object.values(c.headers as Record<string, unknown>).every((val) => typeof val === "string")) return false
  }
  if (c.timeout !== undefined && typeof c.timeout !== "number") return false
  if (c.disabled !== undefined && typeof c.disabled !== "boolean") return false
  if (c.oauth !== undefined) {
    if (typeof c.oauth !== "object" || c.oauth === null || Array.isArray(c.oauth)) return false
    const oauth = c.oauth as Record<string, unknown>
    if (oauth.clientId !== undefined && typeof oauth.clientId !== "string") return false
    if (oauth.clientSecret !== undefined && typeof oauth.clientSecret !== "string") return false
    if (oauth.scope !== undefined && typeof oauth.scope !== "string") return false
    if (oauth.redirectUri !== undefined && typeof oauth.redirectUri !== "string") return false
    if (oauth.useDiscovery !== undefined && typeof oauth.useDiscovery !== "boolean") return false
    if (oauth.useDynamicRegistration !== undefined && typeof oauth.useDynamicRegistration !== "boolean") return false
    if (oauth.serverMetadata !== undefined) {
      if (typeof oauth.serverMetadata !== "object" || oauth.serverMetadata === null || Array.isArray(oauth.serverMetadata)) return false
      const sm = oauth.serverMetadata as Record<string, unknown>
      if (sm.authorization_endpoint !== undefined && typeof sm.authorization_endpoint !== "string") return false
      if (sm.token_endpoint !== undefined && typeof sm.token_endpoint !== "string") return false
      if (sm.issuer !== undefined && typeof sm.issuer !== "string") return false
    }
  }
  return true
}

function isValidMcpServerConfig(config: unknown): config is Partial<ProfileMcpServerConfig> {
  if (config === null || typeof config !== "object" || Array.isArray(config)) return false
  const c = config as Record<string, unknown>
  if (c.disabledServers !== undefined && !isStringArray(c.disabledServers)) return false
  if (c.disabledTools !== undefined && !isStringArray(c.disabledTools)) return false
  if (c.enabledServers !== undefined && !isStringArray(c.enabledServers)) return false
  if (c.enabledRuntimeTools !== undefined && !isStringArray(c.enabledRuntimeTools)) return false
  if (c.allServersDisabledByDefault !== undefined && typeof c.allServersDisabledByDefault !== "boolean") return false
  return true
}

function isValidModelConfig(config: unknown): boolean {
  if (typeof config !== "object" || config === null || Array.isArray(config)) return false
  const c = config as Record<string, unknown>
  for (const field of ["agentProviderId", "mcpToolsProviderId", "transcriptPostProcessingProviderId"]) {
    if (c[field] !== undefined && (typeof c[field] !== "string" || !VALID_PROVIDER_IDS.includes(c[field] as string))) return false
  }
  if (c.sttProviderId !== undefined && (typeof c.sttProviderId !== "string" || !VALID_STT_PROVIDER_IDS.includes(c.sttProviderId as string))) return false
  if (c.ttsProviderId !== undefined && (typeof c.ttsProviderId !== "string" || !VALID_TTS_PROVIDER_IDS.includes(c.ttsProviderId as string))) return false
  for (const field of ["agentOpenaiModel", "agentGroqModel", "agentGeminiModel", "agentChatgptWebModel", "mcpToolsOpenaiModel", "mcpToolsGroqModel", "mcpToolsGeminiModel", "mcpToolsChatgptWebModel", "currentModelPresetId", "openaiSttModel", "groqSttModel", "transcriptPostProcessingOpenaiModel", "transcriptPostProcessingGroqModel", "transcriptPostProcessingGeminiModel", "transcriptPostProcessingChatgptWebModel"]) {
    if (c[field] !== undefined && typeof c[field] !== "string") return false
  }
  return true
}

function isValidSkillsConfig(config: unknown): config is Partial<ProfileSkillsConfig> {
  if (config === null || typeof config !== "object" || Array.isArray(config)) return false
  const c = config as Record<string, unknown>
  if (c.enabledSkillIds !== undefined && !isStringArray(c.enabledSkillIds)) return false
  if (c.allSkillsDisabledByDefault !== undefined && typeof c.allSkillsDisabledByDefault !== "boolean") return false
  return true
}

// ============================================================================
// Conversion Helpers
// ============================================================================

/**
 * Convert AgentProfileToolConfig to ProfileMcpServerConfig.
 * Used when creating session snapshots from AgentProfile.
 */
export function toolConfigToMcpServerConfig(toolConfig?: AgentProfileToolConfig): ProfileMcpServerConfig | undefined {
  if (!toolConfig) return undefined
  return {
    disabledServers: toolConfig.disabledServers,
    disabledTools: toolConfig.disabledTools,
    allServersDisabledByDefault: toolConfig.allServersDisabledByDefault,
    enabledServers: toolConfig.enabledServers,
    enabledRuntimeTools: toolConfig.enabledRuntimeTools,
  }
}

/**
 * Create a SessionProfileSnapshot from an AgentProfile.
 * Used by session creation code to capture profile state at session start.
 */
export function createSessionSnapshotFromProfile(
  profile: AgentProfile,
  skillsInstructions?: string,
): SessionProfileSnapshot {
  return {
    profileId: profile.id,
    profileName: profile.displayName,
    guidelines: profile.guidelines || "",
    systemPrompt: profile.systemPrompt,
    mcpServerConfig: toolConfigToMcpServerConfig(profile.toolConfig),
    modelConfig: profile.modelConfig,
    skillsInstructions,
    agentProperties: profile.properties,
    skillsConfig: profile.skillsConfig,
  }
}

/**
 * Refresh the skills portion of a stored session snapshot from the current
 * profile. Sessions intentionally snapshot most profile settings for isolation,
 * but skill toggles are user-facing access controls that should take effect for
 * follow-up runs without requiring a brand-new conversation.
 */
export function refreshSessionSnapshotSkillsFromProfile(
  snapshot: SessionProfileSnapshot | undefined,
  profile: AgentProfile | undefined,
): SessionProfileSnapshot | undefined {
  if (!snapshot || !profile || snapshot.profileId !== profile.id) return snapshot
  return {
    ...snapshot,
    skillsConfig: profile.skillsConfig,
    // If a caller cached rendered skill instructions in the snapshot, invalidate
    // them so the next prompt is rebuilt from the latest enabled skill IDs.
    skillsInstructions: undefined,
  }
}

/**
 * Type for agent profile conversations storage.
 */
interface AgentProfileConversationsData {
  [profileId: string]: ConversationMessage[]
}

/**
 * Default built-in agents.
 * The "main-agent" is the primary agent that handles all user interactions.
 * Its guidelines come from `.agents/agents.md` via config.mcpToolsSystemPrompt.
 */
const DEFAULT_PROFILES: Omit<AgentProfile, "id" | "createdAt" | "updatedAt">[] = [
  {
    name: "main-agent",
    displayName: "Main Agent",
    description: "The primary agent that handles all user interactions",
    systemPrompt: "You are a highly autonomous and proactive assistant. Answer questions clearly and assist with a wide variety of tasks. Make as many tool calls as needed and do NOT stop to ask for permission unless absolutely necessary.",
    guidelines: "",
    connection: { type: "internal" },
    isStateful: false,
    role: "delegation-target",
    enabled: true,
    isBuiltIn: true,
    isUserProfile: false,
    isAgentTarget: true,
    isDefault: true,
  },
]

/**
 * Service for managing agent profiles.
 * Handles CRUD operations, migration, and queries.
 */
class AgentProfileService {
  private profilesData: AgentProfilesData | undefined
  private conversationsData: AgentProfileConversationsData = {}

  constructor() {
    this.loadProfiles()
    this.loadConversations()
    this.migrateAcpxRuntimeConfig()
  }

  private normalizeConnection(profile: AgentProfile): boolean {
    if (profile.connection.type === 'internal' || profile.connection.type === 'remote') {
      return false
    }

    const legacyConnection = profile.connection as AgentProfile['connection'] & {
      command?: string
      args?: string[]
      env?: Record<string, string>
      cwd?: string
      agent?: string
    }

    profile.connection = {
      type: 'acpx',
      ...(legacyConnection.command || legacyConnection.agent ? {} : { agent: profile.name }),
      ...(legacyConnection.agent ? { agent: legacyConnection.agent } : {}),
      ...(legacyConnection.command ? { command: legacyConnection.command } : {}),
      ...(legacyConnection.args ? { args: legacyConnection.args } : {}),
      ...(legacyConnection.env ? { env: legacyConnection.env } : {}),
      ...(legacyConnection.cwd ? { cwd: legacyConnection.cwd } : {}),
    }

    return true
  }

  private migrateAcpxRuntimeConfig(): void {
    if (!this.profilesData) return

    let profilesChanged = false
    const seenNames = new Set(this.profilesData.profiles.map(profile => profile.name))

    for (const profile of this.profilesData.profiles) {
      profilesChanged = this.normalizeConnection(profile) || profilesChanged
    }

    const config = configStore.get()
    let configChanged = false
    const nextConfig = { ...config } as typeof config & { acpInjectRuntimeTools?: boolean }

    const legacyMainAgentMode = (config as { mainAgentMode?: string }).mainAgentMode
    if (legacyMainAgentMode === 'acp') {
      nextConfig.mainAgentMode = 'acpx'
      configChanged = true
    }

    if (Array.isArray(nextConfig.acpAgents) && nextConfig.acpAgents.length > 0) {
      for (const legacyAgent of nextConfig.acpAgents) {
        if (legacyAgent.connection.type === 'remote') {
          logApp(`[AgentProfileService] Skipping unsupported legacy remote ACP agent during acpx migration: ${legacyAgent.name}`)
          continue
        }

        if (seenNames.has(legacyAgent.name)) continue
        this.profilesData.profiles.push(acpAgentConfigToAgentProfile(legacyAgent))
        seenNames.add(legacyAgent.name)
        profilesChanged = true
      }

      delete nextConfig.acpAgents
      configChanged = true
    }

    if ('acpInjectRuntimeTools' in nextConfig) {
      delete nextConfig.acpInjectRuntimeTools
      configChanged = true
    }

    if (profilesChanged) {
      this.saveProfiles()
    }

    if (configChanged) {
      configStore.save(nextConfig)
    }
  }

  /**
   * Load profiles from storage, migrating from legacy formats if needed.
   *
   * Priority:
   * 1. `.agents/agents/` modular files (global + workspace overlay)
   * 2. `agent-profiles.json` (legacy monolithic file — triggers migration to modular)
   * 3. Legacy `profiles.json` / `personas.json` (very old formats)
   * 4. Built-in defaults
   */
  private loadProfiles(): AgentProfilesData {
    // 1. Try loading from modular .agents/agents/ directory
    const globalLayer = getAgentsLayerPaths(globalAgentsFolder)
    const globalResult = loadAgentProfilesLayer(globalLayer)

    const workspaceDir = resolveWorkspaceAgentsFolder()
    let workspaceProfiles: AgentProfile[] = []
    if (workspaceDir) {
      const workspaceLayer = getAgentsLayerPaths(workspaceDir)
      const workspaceResult = loadAgentProfilesLayer(workspaceLayer)
      workspaceProfiles = workspaceResult.profiles
    }

    if (globalResult.profiles.length > 0 || workspaceProfiles.length > 0) {
      // Merge: workspace overrides global by ID
      const mergedById = new Map<string, AgentProfile>()
      for (const p of globalResult.profiles) mergedById.set(p.id, p)
      for (const p of workspaceProfiles) mergedById.set(p.id, p) // workspace wins

      // Also load currentProfileId from legacy JSON if available
      let currentProfileId: string | undefined
      try {
        if (fs.existsSync(agentProfilesPath)) {
          const legacyData = JSON.parse(fs.readFileSync(agentProfilesPath, "utf8")) as AgentProfilesData
          currentProfileId = legacyData.currentProfileId
        }
      } catch { /* best-effort */ }

      this.profilesData = {
        profiles: Array.from(mergedById.values()),
        currentProfileId,
      }
      logApp(`Loaded ${this.profilesData.profiles.length} agent profile(s) from .agents/agents/`)
      return this.profilesData
    }

    // 2. Try loading from legacy agent-profiles.json and migrate to modular
    try {
      if (fs.existsSync(agentProfilesPath)) {
        const data = JSON.parse(fs.readFileSync(agentProfilesPath, "utf8")) as AgentProfilesData
        this.profilesData = data
        // Migrate: write each profile as modular files
        this.migrateToModularFiles(data.profiles)
        return data
      }
    } catch (error) {
      logApp("Error loading agent profiles:", error)
    }

    // 3. Try to migrate from very old legacy formats
    const migratedProfiles = this.migrateFromLegacy()
    if (migratedProfiles.length > 0) {
      this.profilesData = { profiles: migratedProfiles }
      this.saveProfiles()
      return this.profilesData
    }

    // 4. Initialize with defaults
    const now = Date.now()
    const defaultProfiles: AgentProfile[] = DEFAULT_PROFILES.map((p) => ({
      ...p,
      id: randomUUID(),
      createdAt: now,
      updatedAt: now,
    }))

    this.profilesData = { profiles: defaultProfiles }
    this.saveProfiles()
    return this.profilesData
  }

  /**
   * One-time migration: split agent-profiles.json into .agents/agents/ files.
   */
  private migrateToModularFiles(profiles: AgentProfile[]): void {
    try {
      const globalLayer = getAgentsLayerPaths(globalAgentsFolder)
      writeAllAgentsProfileFiles(globalLayer, profiles, { onlyIfMissing: true, maxBackups: 10 })
      logApp(`Migrated ${profiles.length} agent profile(s) to .agents/agents/`)
    } catch (error) {
      logApp("Error migrating agent profiles to modular files:", error)
    }
  }

  /**
   * Migrate from legacy Profile, Persona, and ACPAgentConfig formats (one-time migration).
   */
  private migrateFromLegacy(): AgentProfile[] {
    const migrated: AgentProfile[] = []
    const seenIds = new Set<string>()

    // Migrate legacy Profile records into chat-agent profiles.
    try {
      if (fs.existsSync(legacyProfilesPath)) {
        const data = JSON.parse(fs.readFileSync(legacyProfilesPath, "utf8")) as ProfilesData
        for (const profile of data.profiles) {
          if (!seenIds.has(profile.id)) {
            const agentProfile = profileToAgentProfile(profile)
            // Preserve currentProfileId as isDefault
            if (data.currentProfileId === profile.id) {
              agentProfile.isDefault = true
            }
            migrated.push(agentProfile)
            seenIds.add(profile.id)
          }
        }
        logApp(`Migrated ${data.profiles.length} legacy profiles`)
      }
    } catch (error) {
      logApp("Error migrating legacy profiles:", error)
    }

    // Migrate legacy personas/agents (agent targets)
    try {
      if (fs.existsSync(legacyPersonasPath)) {
        const data = JSON.parse(fs.readFileSync(legacyPersonasPath, "utf8")) as PersonasData
        for (const persona of data.personas) {
          if (!seenIds.has(persona.id)) {
            migrated.push(personaToAgentProfile(persona))
            seenIds.add(persona.id)
          }
        }
        logApp(`Migrated ${data.personas.length} legacy agents (from personas.json)`)
      }
    } catch (error) {
      logApp("Error migrating legacy agents:", error)
    }

    // Migrate ACP agents from config
    try {
      const config = configStore.get()
      if (config.acpAgents) {
        for (const acpAgent of config.acpAgents) {
          if (!seenIds.has(acpAgent.name)) {
            migrated.push(acpAgentConfigToAgentProfile(acpAgent))
            seenIds.add(acpAgent.name)
          }
        }
        logApp(`Migrated ${config.acpAgents.length} legacy ACP agents`)
      }
    } catch (error) {
      logApp("Error migrating legacy ACP agents:", error)
    }

    return migrated
  }

  /**
   * Save profiles to storage.
   */
  private saveProfiles(): void {
    if (!this.profilesData) return
    try {
      // Canonical: write modular .agents/agents/ files
      const globalLayer = getAgentsLayerPaths(globalAgentsFolder)
      writeAllAgentsProfileFiles(globalLayer, this.profilesData.profiles, { maxBackups: 10 })

      // Shadow: keep legacy agent-profiles.json for backward compatibility
      fs.writeFileSync(agentProfilesPath, JSON.stringify(this.profilesData, null, 2))
    } catch (error) {
      logApp("Error saving agent profiles:", error)
    }
  }

  /**
   * Save a single profile to modular files (used after individual updates).
   */
  private saveSingleProfile(profile: AgentProfile): void {
    try {
      const globalLayer = getAgentsLayerPaths(globalAgentsFolder)
      writeAgentsProfileFiles(globalLayer, profile, { maxBackups: 10 })
    } catch (error) {
      logApp("Error saving agent profile to modular files:", error)
    }
  }

  /**
   * Load conversations from storage.
   */
  private loadConversations(): void {
    try {
      if (fs.existsSync(agentProfileConversationsPath)) {
        this.conversationsData = JSON.parse(
          fs.readFileSync(agentProfileConversationsPath, "utf8")
        )
      }
    } catch (error) {
      logApp("Error loading agent profile conversations:", error)
    }
  }

  /**
   * Save conversations to storage.
   */
  private saveConversations(): void {
    try {
      fs.writeFileSync(
        agentProfileConversationsPath,
        JSON.stringify(this.conversationsData, null, 2)
      )
    } catch (error) {
      logApp("Error saving agent profile conversations:", error)
    }
  }

  // ============================================================================
  // CRUD Operations
  // ============================================================================

  /**
   * Get all profiles.
   */
  getAll(): AgentProfile[] {
    return this.profilesData?.profiles ?? []
  }

  /**
   * Get a profile by ID.
   */
  getById(id: string): AgentProfile | undefined {
    return this.profilesData?.profiles.find((p) => p.id === id)
  }

  /**
   * Get a profile by name.
   */
  getByName(name: string): AgentProfile | undefined {
    // Try exact match on name first, then displayName for flexibility
    // (handles both old slugged names and new display names)
    return this.profilesData?.profiles.find((p) => p.name === name)
      || this.profilesData?.profiles.find((p) => p.displayName === name)
  }

  /**
   * Create a new profile.
   */
  create(profile: Omit<AgentProfile, "id" | "createdAt" | "updatedAt">): AgentProfile {
    const now = Date.now()
    const newProfile: AgentProfile = {
      ...profile,
      // Use displayName as the canonical name (no slug transformation)
      name: profile.name || profile.displayName,
      id: randomUUID(),
      createdAt: now,
      updatedAt: now,
    }

    if (!this.profilesData) {
      this.profilesData = { profiles: [] }
    }
    this.profilesData.profiles.push(newProfile)
    this.saveProfiles()

    return newProfile
  }

  /**
   * Update a profile.
   */
  update(id: string, updates: Partial<AgentProfile>): AgentProfile | undefined {
    const profile = this.getById(id)
    if (!profile) return undefined

    // Don't allow updating certain fields
    const { id: _, createdAt, isBuiltIn, ...allowedUpdates } = updates

    // Keep name in sync with displayName (skip for built-in agents)
    if (allowedUpdates.displayName && !profile.isBuiltIn) {
      allowedUpdates.name = allowedUpdates.displayName
    }

    Object.assign(profile, allowedUpdates, { updatedAt: Date.now() })
    this.saveProfiles()

    return profile
  }

  /**
   * Delete a profile.
   */
  delete(id: string): boolean {
    if (!this.profilesData) return false

    const profile = this.getById(id)
    if (!profile || profile.isBuiltIn) return false

    const index = this.profilesData.profiles.findIndex((p) => p.id === id)
    if (index === -1) return false

    this.profilesData.profiles.splice(index, 1)
    this.saveProfiles()

    // Delete modular files from .agents/agents/
    try {
      const globalLayer = getAgentsLayerPaths(globalAgentsFolder)
      deleteAgentProfileFiles(globalLayer, id)
    } catch (error) {
      logApp("Error deleting agent profile files:", error)
    }

    // Also delete conversation
    delete this.conversationsData[id]
    this.saveConversations()

    return true
  }

  // ============================================================================
  // Query Methods
  // ============================================================================

  /**
   * Get profiles by role.
   * Uses the new role field, falling back to legacy flags for backward compatibility.
   */
  getByRole(role: AgentProfileRole): AgentProfile[] {
    return this.getAll().filter((p) => {
      const requestedRole = normalizeAgentProfileRole(role)

      // Use role field if present
      if (p.role) {
        return normalizeAgentProfileRole(p.role) === requestedRole
      }
      // Fall back to legacy flags for backward compatibility
      switch (role) {
        case "chat-agent":
        case "user-profile":
          return p.isUserProfile === true
        case "delegation-target":
          return p.isAgentTarget === true
        case "external-agent":
          // External agents have acpx/remote connection types and are agent targets
          return p.isAgentTarget === true &&
            (p.connection.type === 'acpx' || p.connection.type === 'remote')
        default:
          return false
      }
    })
  }

  /**
   * Get chat agents (shown in the agent picker).
   * Uses getByRole internally for consistency.
   */
  getUserProfiles(): AgentProfile[] {
    // Use getByRole, but also include legacy isUserProfile for backward compatibility
    const byRole = this.getByRole("chat-agent")
    const byLegacy = this.getAll().filter((p) => p.isUserProfile && !p.role)
    // Combine and deduplicate by id
    const ids = new Set(byRole.map(p => p.id))
    return [...byRole, ...byLegacy.filter(p => !ids.has(p.id))]
  }

  /**
   * Get agent targets (available for delegation).
   * Uses getByRole internally for consistency.
   */
  getAgentTargets(): AgentProfile[] {
    // Use getByRole, but also include legacy isAgentTarget for backward compatibility
    const byRole = this.getByRole("delegation-target")
    const byLegacy = this.getAll().filter((p) => p.isAgentTarget && !p.role)
    // Combine and deduplicate by id
    const ids = new Set(byRole.map(p => p.id))
    return [...byRole, ...byLegacy.filter(p => !ids.has(p.id))]
  }

  /**
   * Get external agents (acpx/remote agents).
   */
  getExternalAgents(): AgentProfile[] {
    return this.getAll().filter((profile) =>
      profile.role === "external-agent"
      || profile.connection.type === 'acpx'
      || profile.connection.type === "remote"
    )
  }

  /**
   * Get enabled agent targets.
   */
  getEnabledAgentTargets(): AgentProfile[] {
    return this.getAgentTargets().filter((p) => p.enabled)
  }

  /**
   * Get the current active agent profile.
   * Falls back to the default agent if no current profile is set.
   */
  getCurrentProfile(): AgentProfile | undefined {
    const currentId = this.profilesData?.currentProfileId
    if (currentId) {
      return this.getById(currentId)
    }
    // Fall back to any default profile
    const defaultProfile = this.getAll().find((p) => p.isDefault)
    if (defaultProfile) return defaultProfile
    // Fall back to the built-in profile (handles migrated data without isDefault set)
    return this.getAll().find((p) => p.isBuiltIn)
  }

  /**
   * Set the current active agent profile.
   */
  setCurrentProfile(id: string): void {
    if (!this.profilesData) return
    const profile = this.getById(id)
    if (profile) {
      this.profilesData.currentProfileId = id
      this.saveProfiles()
    }
  }

  // ============================================================================
  // Conversation State (for stateful agents)
  // ============================================================================

  /**
   * Get conversation for a profile.
   */
  getConversation(profileId: string): ConversationMessage[] {
    return this.conversationsData[profileId] ?? []
  }

  /**
   * Set conversation for a profile.
   */
  setConversation(profileId: string, messages: ConversationMessage[]): void {
    this.conversationsData[profileId] = messages
    this.saveConversations()
  }

  /**
   * Add message to a profile's conversation.
   */
  addToConversation(profileId: string, message: ConversationMessage): void {
    if (!this.conversationsData[profileId]) {
      this.conversationsData[profileId] = []
    }
    this.conversationsData[profileId].push(message)
    this.saveConversations()
  }

  /**
   * Clear conversation for a profile.
   */
  clearConversation(profileId: string): void {
    delete this.conversationsData[profileId]
    this.saveConversations()

    // Also clear conversationId on the profile
    const profile = this.getById(profileId)
    if (profile) {
      profile.conversationId = undefined
      this.saveProfiles()
    }
  }

  /**
   * Reload profiles from disk (for external changes).
   */
  reload(): void {
    this.profilesData = undefined
    this.loadProfiles()
    this.loadConversations()
  }

  // ============================================================================
  // Legacy ACP integration shim
  // ============================================================================

  syncAgentProfilesToACPRegistry(): void {
    logApp('[AgentProfileService] ACP registry sync is disabled; acpx agent profiles are used directly')
  }

  // ============================================================================
  // MCP Config Management (ported from ProfileService)
  // ============================================================================

  /**
   * Update the tool/MCP server configuration for a profile.
   * Merges with existing config - only provided fields are updated.
   * Accepts ProfileMcpServerConfig for backward compatibility with callers.
   */
  updateProfileMcpConfig(id: string, mcpServerConfig: Partial<ProfileMcpServerConfig>): AgentProfile | undefined {
    const profile = this.getById(id)
    if (!profile) return undefined

    const existing = profile.toolConfig ?? {}
    const mergedToolConfig: AgentProfileToolConfig = {
      ...existing,
      ...(mcpServerConfig.disabledServers !== undefined && { disabledServers: mcpServerConfig.disabledServers }),
      ...(mcpServerConfig.disabledTools !== undefined && { disabledTools: mcpServerConfig.disabledTools }),
      ...(mcpServerConfig.allServersDisabledByDefault !== undefined && { allServersDisabledByDefault: mcpServerConfig.allServersDisabledByDefault }),
      ...(mcpServerConfig.enabledServers !== undefined && { enabledServers: mcpServerConfig.enabledServers }),
      ...(mcpServerConfig.enabledRuntimeTools !== undefined && {
        // Empty array is treated as "not configured" (filesystem-first default runtime tools) — clear persisted whitelist.
        enabledRuntimeTools: mcpServerConfig.enabledRuntimeTools.length > 0
          ? mcpServerConfig.enabledRuntimeTools
          : undefined,
      }),
    }

    return this.update(id, { toolConfig: mergedToolConfig })
  }

  /**
   * Save current MCP state to a profile.
   */
  saveCurrentMcpStateToProfile(
    id: string,
    disabledServers: string[],
    disabledTools: string[],
    enabledServers?: string[],
    enabledRuntimeTools?: string[],
  ): AgentProfile | undefined {
    return this.updateProfileMcpConfig(id, {
      disabledServers,
      disabledTools,
      ...(enabledServers !== undefined && { enabledServers }),
      ...(enabledRuntimeTools !== undefined && { enabledRuntimeTools }),
    })
  }

  // ============================================================================
  // Model Config Management (ported from ProfileService)
  // ============================================================================

  /**
   * Update the model configuration for a profile.
   * Merges with existing config - only provided fields are updated.
   */
  updateProfileModelConfig(id: string, modelConfig: Partial<ProfileModelConfig>): AgentProfile | undefined {
    const profile = this.getById(id)
    if (!profile) return undefined

    const mergedModelConfig: ProfileModelConfig = {
      ...(profile.modelConfig ?? {}),
      ...(modelConfig.agentProviderId !== undefined && { agentProviderId: modelConfig.agentProviderId, mcpToolsProviderId: modelConfig.agentProviderId }),
      ...(modelConfig.agentOpenaiModel !== undefined && { agentOpenaiModel: modelConfig.agentOpenaiModel, mcpToolsOpenaiModel: modelConfig.agentOpenaiModel }),
      ...(modelConfig.agentGroqModel !== undefined && { agentGroqModel: modelConfig.agentGroqModel, mcpToolsGroqModel: modelConfig.agentGroqModel }),
      ...(modelConfig.agentGeminiModel !== undefined && { agentGeminiModel: modelConfig.agentGeminiModel, mcpToolsGeminiModel: modelConfig.agentGeminiModel }),
      ...(modelConfig.agentChatgptWebModel !== undefined && { agentChatgptWebModel: modelConfig.agentChatgptWebModel, mcpToolsChatgptWebModel: modelConfig.agentChatgptWebModel }),
      ...(modelConfig.mcpToolsProviderId !== undefined && { mcpToolsProviderId: modelConfig.mcpToolsProviderId, agentProviderId: modelConfig.mcpToolsProviderId }),
      ...(modelConfig.mcpToolsOpenaiModel !== undefined && { mcpToolsOpenaiModel: modelConfig.mcpToolsOpenaiModel, agentOpenaiModel: modelConfig.mcpToolsOpenaiModel }),
      ...(modelConfig.mcpToolsGroqModel !== undefined && { mcpToolsGroqModel: modelConfig.mcpToolsGroqModel, agentGroqModel: modelConfig.mcpToolsGroqModel }),
      ...(modelConfig.mcpToolsGeminiModel !== undefined && { mcpToolsGeminiModel: modelConfig.mcpToolsGeminiModel, agentGeminiModel: modelConfig.mcpToolsGeminiModel }),
      ...(modelConfig.mcpToolsChatgptWebModel !== undefined && { mcpToolsChatgptWebModel: modelConfig.mcpToolsChatgptWebModel, agentChatgptWebModel: modelConfig.mcpToolsChatgptWebModel }),
      ...(modelConfig.currentModelPresetId !== undefined && { currentModelPresetId: modelConfig.currentModelPresetId }),
      ...(modelConfig.sttProviderId !== undefined && { sttProviderId: modelConfig.sttProviderId }),
      ...(modelConfig.openaiSttModel !== undefined && { openaiSttModel: modelConfig.openaiSttModel }),
      ...(modelConfig.groqSttModel !== undefined && { groqSttModel: modelConfig.groqSttModel }),
      ...(modelConfig.transcriptPostProcessingProviderId !== undefined && { transcriptPostProcessingProviderId: modelConfig.transcriptPostProcessingProviderId }),
      ...(modelConfig.transcriptPostProcessingOpenaiModel !== undefined && { transcriptPostProcessingOpenaiModel: modelConfig.transcriptPostProcessingOpenaiModel }),
      ...(modelConfig.transcriptPostProcessingGroqModel !== undefined && { transcriptPostProcessingGroqModel: modelConfig.transcriptPostProcessingGroqModel }),
      ...(modelConfig.transcriptPostProcessingGeminiModel !== undefined && { transcriptPostProcessingGeminiModel: modelConfig.transcriptPostProcessingGeminiModel }),
      ...(modelConfig.transcriptPostProcessingChatgptWebModel !== undefined && { transcriptPostProcessingChatgptWebModel: modelConfig.transcriptPostProcessingChatgptWebModel }),
      ...(modelConfig.ttsProviderId !== undefined && { ttsProviderId: modelConfig.ttsProviderId }),
    }

    return this.update(id, { modelConfig: mergedModelConfig })
  }

  /**
   * Save current model state to a profile.
   */
  saveCurrentModelStateToProfile(id: string, modelConfig: ProfileModelConfig): AgentProfile | undefined {
    return this.updateProfileModelConfig(id, modelConfig)
  }

  // ============================================================================
  // Skills Management (ported from ProfileService)
  // ============================================================================

  /**
   * Update the skills configuration for a profile.
   * Merges with existing config - only provided fields are updated.
   */
  updateProfileSkillsConfig(id: string, skillsConfig: Partial<ProfileSkillsConfig>): AgentProfile | undefined {
    const profile = this.getById(id)
    if (!profile) return undefined

    const mergedSkillsConfig: ProfileSkillsConfig = {
      ...(profile.skillsConfig ?? {}),
      ...(skillsConfig.enabledSkillIds !== undefined && { enabledSkillIds: skillsConfig.enabledSkillIds }),
      ...(skillsConfig.allSkillsDisabledByDefault !== undefined && { allSkillsDisabledByDefault: skillsConfig.allSkillsDisabledByDefault }),
    }

    return this.update(id, { skillsConfig: mergedSkillsConfig })
  }

  /**
   * Toggle a skill's enabled state for a specific profile.
   * When transitioning from "all enabled by default" (unconfigured), populates
   * the enabled list with all skills minus the toggled one.
   */
  toggleProfileSkill(profileId: string, skillId: string, allSkillIds?: string[]): AgentProfile | undefined {
    const profile = this.getById(profileId)
    if (!profile) return undefined

    // If profile has no explicit skills config (all enabled by default)
    if (!profile.skillsConfig || !profile.skillsConfig.allSkillsDisabledByDefault) {
      // Transitioning from "all enabled" to opt-in: enable all EXCEPT the toggled skill
      const allIds = allSkillIds ?? []
      const newEnabledSkillIds = allIds.filter(id => id !== skillId)
      return this.updateProfileSkillsConfig(profileId, {
        enabledSkillIds: newEnabledSkillIds,
        allSkillsDisabledByDefault: true,
      })
    }

    const currentEnabledSkills = profile.skillsConfig.enabledSkillIds ?? []
    const isCurrentlyEnabled = currentEnabledSkills.includes(skillId)

    const newEnabledSkillIds = isCurrentlyEnabled
      ? currentEnabledSkills.filter(id => id !== skillId)
      : [...currentEnabledSkills, skillId]

    const allAvailableSkillIds = new Set(allSkillIds ?? [])
    const hasAllAvailableSkillsEnabled = allAvailableSkillIds.size > 0
      && Array.from(allAvailableSkillIds).every(id => newEnabledSkillIds.includes(id))

    if (hasAllAvailableSkillsEnabled) {
      return this.updateProfileSkillsConfig(profileId, {
        enabledSkillIds: [],
        allSkillsDisabledByDefault: false,
      })
    }

    return this.updateProfileSkillsConfig(profileId, {
      enabledSkillIds: newEnabledSkillIds,
      allSkillsDisabledByDefault: true,
    })
  }

  /**
   * Check if a skill is enabled for a specific profile.
   * When skillsConfig is undefined (unconfigured), all skills are enabled by default.
   */
  isSkillEnabledForProfile(profileId: string, skillId: string): boolean {
    const profile = this.getById(profileId)
    if (!profile) return false
    // No skillsConfig = unconfigured = all skills enabled by default
    if (!profile.skillsConfig || !profile.skillsConfig.allSkillsDisabledByDefault) return true
    return (profile.skillsConfig.enabledSkillIds ?? []).includes(skillId)
  }

  /**
   * Check if a profile has all skills enabled by default (unconfigured).
   */
  hasAllSkillsEnabledByDefault(profileId: string): boolean {
    const profile = this.getById(profileId)
    if (!profile) return false
    return !profile.skillsConfig || !profile.skillsConfig.allSkillsDisabledByDefault
  }

  /**
   * Get all enabled skill IDs for a profile.
   * Returns null when all skills are enabled by default (unconfigured skillsConfig).
   * Callers should interpret null as "all available skills are enabled".
   */
  getEnabledSkillIdsForProfile(profileId: string): string[] | null {
    const profile = this.getById(profileId)
    if (!profile) return []
    // No skillsConfig = unconfigured = all skills enabled
    if (!profile.skillsConfig || !profile.skillsConfig.allSkillsDisabledByDefault) return null
    return profile.skillsConfig.enabledSkillIds ?? []
  }

  /**
   * Enable a skill for the current profile (used when installing new skills).
   * If the profile has no skillsConfig (all skills enabled by default), this is a no-op.
   */
  enableSkillForCurrentProfile(skillId: string): AgentProfile | undefined {
    const currentProfile = this.getCurrentProfile()
    if (!currentProfile) return undefined

    // If all skills are enabled by default (no skillsConfig), no need to add explicitly
    if (!currentProfile.skillsConfig || !currentProfile.skillsConfig.allSkillsDisabledByDefault) {
      return currentProfile
    }

    const currentEnabledSkills = currentProfile.skillsConfig.enabledSkillIds ?? []
    if (currentEnabledSkills.includes(skillId)) return currentProfile

    return this.updateProfileSkillsConfig(currentProfile.id, {
      enabledSkillIds: [...currentEnabledSkills, skillId],
      allSkillsDisabledByDefault: true,
    })
  }

  // ============================================================================
  // Import / Export (ported from ProfileService)
  // ============================================================================

  /**
   * Export a profile as a JSON string.
   */
  exportProfile(id: string): string {
    const profile = this.getById(id)
    if (!profile) throw new Error(`Profile with id ${id} not found`)

    const mcpServerConfig = toolConfigToMcpServerConfig(profile.toolConfig)
    const exportData: Record<string, unknown> = {
      version: 1,
      name: profile.displayName,
      guidelines: profile.guidelines || "",
    }

    if (profile.systemPrompt) exportData.systemPrompt = profile.systemPrompt
    if (mcpServerConfig) exportData.mcpServerConfig = mcpServerConfig
    if (profile.modelConfig) exportData.modelConfig = profile.modelConfig
    if (profile.skillsConfig) exportData.skillsConfig = profile.skillsConfig

    // Include actual MCP server definitions for enabled servers
    const config = configStore.get()
    const mcpConfig = config.mcpConfig
    if (mcpConfig?.mcpServers) {
      const enabledServers: Record<string, unknown> = {}
      const allServerNames = Object.keys(mcpConfig.mcpServers)

      let serversToExport: string[]
      if (mcpServerConfig) {
        if (mcpServerConfig.allServersDisabledByDefault) {
          serversToExport = mcpServerConfig.enabledServers || []
        } else {
          serversToExport = allServerNames.filter(name => !(mcpServerConfig.disabledServers || []).includes(name))
        }
      } else {
        serversToExport = allServerNames
      }

      for (const serverName of serversToExport) {
        if (mcpConfig.mcpServers[serverName]) {
          const { env, headers, oauth, ...sanitizedConfig } = mcpConfig.mcpServers[serverName]
          enabledServers[serverName] = sanitizedConfig
        }
      }

      if (Object.keys(enabledServers).length > 0) {
        exportData.mcpServers = enabledServers
      }
    }

    return JSON.stringify(exportData, null, 2)
  }

  /**
   * Import a profile from a JSON string.
   */
  importProfile(profileJson: string): AgentProfile {
    try {
      const importData = JSON.parse(profileJson)

      if (!importData.name || typeof importData.name !== "string") {
        throw new Error("Invalid profile data: missing or invalid name")
      }
      if (importData.guidelines !== undefined && typeof importData.guidelines !== "string") {
        throw new Error("Invalid profile data: guidelines must be a string")
      }
      if (importData.systemPrompt !== undefined && typeof importData.systemPrompt !== "string") {
        throw new Error("Invalid profile data: systemPrompt must be a string")
      }

      // Create default tool config with all servers disabled
      const appConfig = configStore.get()
      const allServerNames = Object.keys(appConfig.mcpConfig?.mcpServers || {})
      const runtimeToolNames = getRuntimeToolNames()

      const newProfile = this.create({
        name: importData.name,
        displayName: importData.name,
        guidelines: importData.guidelines || "",
        systemPrompt: importData.systemPrompt,
        connection: { type: "internal" },
        role: "delegation-target",
        enabled: true,
        isUserProfile: false,
        isAgentTarget: true,
        toolConfig: {
          disabledServers: allServerNames,
            disabledTools: runtimeToolNames,
          allServersDisabledByDefault: true,
        },
      })

      // Import MCP server definitions if present
      const importedServerNames: string[] = []
      if (importData.mcpServers && typeof importData.mcpServers === "object" && !Array.isArray(importData.mcpServers)) {
        const currentMcpServers = appConfig.mcpConfig?.mcpServers || {}
        const mergedServers = { ...currentMcpServers }
        let newServersAdded = 0

        for (const [serverName, serverConfig] of Object.entries(importData.mcpServers)) {
          const normalizedServerName = serverName.trim()
          if (!normalizedServerName) continue
          if (["__proto__", "constructor", "prototype"].includes(normalizedServerName)) continue
          if (RESERVED_SERVER_NAMES.some(r => r.toLowerCase() === normalizedServerName.toLowerCase())) continue
          if (!mergedServers[normalizedServerName]) {
            if (!isValidServerConfig(serverConfig)) continue
            mergedServers[normalizedServerName] = serverConfig as MCPServerConfig
            importedServerNames.push(normalizedServerName)
            newServersAdded++
          }
        }

        if (newServersAdded > 0) {
          configStore.save({ ...appConfig, mcpConfig: { ...appConfig.mcpConfig, mcpServers: mergedServers } })
          logApp(`Imported ${newServersAdded} new MCP server(s)`)
        }
      }

      // Apply MCP server configuration if present
      if (importData.mcpServerConfig && typeof importData.mcpServerConfig === "object") {
        if (isValidMcpServerConfig(importData.mcpServerConfig)) {
          this.updateProfileMcpConfig(newProfile.id, importData.mcpServerConfig)
        }
      } else if (importedServerNames.length > 0) {
        const current = this.getById(newProfile.id)
        const currentEnabled = current?.toolConfig?.enabledServers || []
        this.updateProfileMcpConfig(newProfile.id, {
          enabledServers: [...new Set([...currentEnabled, ...importedServerNames])],
        })
      }

      // Apply model configuration if present
      if (importData.modelConfig && typeof importData.modelConfig === "object") {
        if (isValidModelConfig(importData.modelConfig)) {
          this.updateProfileModelConfig(newProfile.id, importData.modelConfig)
        }
      }

      // Apply skills configuration if present
      if (importData.skillsConfig && typeof importData.skillsConfig === "object") {
        if (isValidSkillsConfig(importData.skillsConfig)) {
          this.updateProfileSkillsConfig(newProfile.id, importData.skillsConfig)
        }
      }

      return this.getById(newProfile.id)!
    } catch (error) {
      throw new Error(`Failed to import profile: ${error instanceof Error ? error.message : String(error)}`)
    }
  }

  // ============================================================================
  // Backward Compatibility Helpers
  // ============================================================================

  /**
   * Get profiles in legacy Profile format (for backward-compatible IPC handlers).
   * Returns only chat-agent profiles shaped like the legacy Profile type.
   */
  getProfilesLegacy(): Profile[] {
    return this.getUserProfiles().map(p => this.agentProfileToLegacyProfile(p))
  }

  /**
   * Get a single profile in legacy Profile format.
   */
  getProfileLegacy(id: string): Profile | undefined {
    const profile = this.getById(id)
    if (!profile) return undefined
    return this.agentProfileToLegacyProfile(profile)
  }

  /**
   * Get current profile in legacy Profile format.
   */
  getCurrentProfileLegacy(): Profile | undefined {
    const profile = this.getCurrentProfile()
    if (!profile) return undefined
    return this.agentProfileToLegacyProfile(profile)
  }

  /**
   * Convert an AgentProfile to legacy Profile format.
   */
  private agentProfileToLegacyProfile(p: AgentProfile): Profile {
    return {
      id: p.id,
      name: p.displayName,
      guidelines: p.guidelines || "",
      createdAt: p.createdAt,
      updatedAt: p.updatedAt,
      isDefault: p.isDefault,
      mcpServerConfig: toolConfigToMcpServerConfig(p.toolConfig),
      modelConfig: p.modelConfig,
      skillsConfig: p.skillsConfig,
      systemPrompt: p.systemPrompt,
    }
  }

  /**
   * Create an agent with legacy-style parameters.
   * Used by backward-compatible IPC handlers and runtime tools.
   */
  createUserProfile(name: string, guidelines: string, systemPrompt?: string): AgentProfile {
    const config = configStore.get()
    const allServerNames = Object.keys(config.mcpConfig?.mcpServers || {})
    const runtimeToolNames = getRuntimeToolNames()

    return this.create({
      name,
      displayName: name,
      guidelines,
      systemPrompt,
      connection: { type: "internal" },
      role: "delegation-target",
      enabled: true,
      isUserProfile: false,
      isAgentTarget: true,
      toolConfig: {
        disabledServers: allServerNames,
          disabledTools: runtimeToolNames,
        allServersDisabledByDefault: true,
      },
    })
  }

  /**
   * Set current profile and return it (throws if not found, like legacy ProfileService).
   */
  setCurrentProfileStrict(id: string): AgentProfile {
    const profile = this.getById(id)
    if (!profile) throw new Error(`Profile with id ${id} not found`)
    this.setCurrentProfile(id)
    return profile
  }
}

export const agentProfileService = new AgentProfileService()
